Skip to content

Commit 8a8c638

Browse files
theStackjonatack
andcommitted
zmq test: fix sync-up by matching notification to generated block
It turned out that the "sync up" procedure of repeatedly generating a block and waiting for a notification once with timeout is too naive in its current form, as the following scenario could happen: - generate block A - receive notification, timeout happens -> repeat procedure - generate block B - node publishes block A notification - receive notification, we receive the one caused by block A -> sync-up procedure is completed - node publishes block B - the actual test starts - on the first notification reception, one caused by block B is received, rather than the one actually caused by test code, leading to failure This change ensures that after each test block generation, we wait for the notification that is actually caused by that block and ignore others from possibly earlier blocks. Co-authored-by: Jon Atack <[email protected]>
1 parent fb67cae commit 8a8c638

File tree

1 file changed

+34
-8
lines changed

1 file changed

+34
-8
lines changed

test/functional/interface_zmq.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,31 @@ def receive_sequence(self):
6262
return (hash, label, mempool_sequence)
6363

6464

65+
class ZMQTestSetupBlock:
66+
"""Helper class for setting up a ZMQ test via the "sync up" procedure.
67+
Generates a block on the specified node on instantiation and provides a
68+
method to check whether a ZMQ notification matches, i.e. the event was
69+
caused by this generated block. Assumes that a notification either contains
70+
the generated block's hash, it's (coinbase) transaction id, the raw block or
71+
raw transaction data.
72+
"""
73+
74+
def __init__(self, node):
75+
self.block_hash = node.generate(1)[0]
76+
coinbase = node.getblock(self.block_hash, 2)['tx'][0]
77+
self.tx_hash = coinbase['txid']
78+
self.raw_tx = coinbase['hex']
79+
self.raw_block = node.getblock(self.block_hash, 0)
80+
81+
def caused_notification(self, notification):
82+
return (
83+
self.block_hash in notification
84+
or self.tx_hash in notification
85+
or self.raw_block in notification
86+
or self.raw_tx in notification
87+
)
88+
89+
6590
class ZMQTest (BitcoinTestFramework):
6691
def set_test_params(self):
6792
self.num_nodes = 2
@@ -105,17 +130,18 @@ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True):
105130
# Ensure that all zmq publisher notification interfaces are ready by
106131
# running the following "sync up" procedure:
107132
# 1. Generate a block on the node
108-
# 2. Try to receive a notification on all subscribers
109-
# 3. If all subscribers get a message within the timeout (1 second),
133+
# 2. Try to receive the corresponding notification on all subscribers
134+
# 3. If all subscribers get the message within the timeout (1 second),
110135
# we are done, otherwise repeat starting from step 1
111136
for sub in subscribers:
112137
sub.socket.set(zmq.RCVTIMEO, 1000)
113138
while True:
114-
self.nodes[0].generate(1)
139+
test_block = ZMQTestSetupBlock(self.nodes[0])
115140
recv_failed = False
116141
for sub in subscribers:
117142
try:
118-
sub.receive()
143+
while not test_block.caused_notification(sub.receive().hex()):
144+
self.log.debug("Ignoring sync-up notification for previously generated block.")
119145
except zmq.error.Again:
120146
self.log.debug("Didn't receive sync-up notification, trying again.")
121147
recv_failed = True
@@ -340,7 +366,7 @@ def test_sequence(self):
340366
block_count = self.nodes[0].getblockcount()
341367
best_hash = self.nodes[0].getbestblockhash()
342368
self.nodes[0].invalidateblock(best_hash)
343-
sleep(2) # Bit of room to make sure transaction things happened
369+
sleep(2) # Bit of room to make sure transaction things happened
344370

345371
# Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective
346372
# of the time they were gathered.
@@ -389,8 +415,8 @@ def test_sequence(self):
389415
assert_equal(label, "A")
390416
# More transactions to be simply mined
391417
for i in range(len(more_tx)):
392-
assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence())
393-
mempool_seq += 1
418+
assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence())
419+
mempool_seq += 1
394420
# Bumped by rbf
395421
assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())
396422
mempool_seq += 1
@@ -405,7 +431,7 @@ def test_sequence(self):
405431
assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())
406432
mempool_seq += 1
407433
self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
408-
self.sync_all() # want to make sure we didn't break "consensus" for other tests
434+
self.sync_all() # want to make sure we didn't break "consensus" for other tests
409435

410436
def test_mempool_sync(self):
411437
"""

0 commit comments

Comments
 (0)