Skip to content

Commit cfce346

Browse files
author
MarcoFalke
committed
Merge #21310: zmq test: fix sync-up by matching notification to generated block
8a8c638 zmq test: fix sync-up by matching notification to generated block (Sebastian Falbesoner) Pull request description: This is a follow-up PR for #21008, fixes #21216. In the course of investigating the problem with jnewbery (analyzing the Cirrus log https://cirrus-ci.com/task/4660108304056320), it turned out that the "sync up" procedure of repeatedly generating a block and waiting for a notification with timeout is too brittle 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 notification - the actual test starts - on the first notification reception, the one caused by block B is received, rather than the one actually caused by test code => assertion failure This change in the PR 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. The matching is kind of ugly, it assumes that one out of four components in the block is contained in the notification: the block hash, the tx id, the raw block data or the raw transaction data. (Unfortunately we have to support all publisher topics.) I'm aware that this is quite a lot of code now only for establishing a robust test setup. OTOH I wouldn't know of a better method right now, suggestions are very welcome. Note for potential reviewers: for both reproducing the issue on master branch and verifying on PR branch, one can simply generate two blocks in the sync-up procedure rather than one. ACKs for top commit: MarcoFalke: Concept ACK 8a8c638 Tree-SHA512: a2eb78ba06dfd0fda7b1c111b6bbfb5dab4ab08500cc19c7ea02c3239495d5c74cc7d45250a8b3ecc78ca42d97ee6719bf73db8a137839e5e09a3cfcf08ed29e
2 parents 72e6979 + 8a8c638 commit cfce346

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)