Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
252self.log.info("Test the getzmqnotifications RPC")253assert_equal(self.nodes[0].getzmqnotifications(), [254{"type": "pubhashblock", "address": address, "hwm": 1000},255{"type": "pubhashtx", "address": address, "hwm": 1000},256{"type": "pubrawblock", "address": address, "hwm": 1000},257{"type": "pubrawtx", "address": address, "hwm": 1000},258])260assert_equal(self.nodes[1].getzmqnotifications(), [])261if unix:262os.unlink(socket_path)264def test_reorg(self):266address = f"tcp://127.0.0.1:{self.zmq_port_base}"268# Should only notify the tip if a reorg occurs269hashblock, hashtx = self.setup_zmq_test(270[(topic, address) for topic in ["hashblock", "hashtx"]],271recv_timeout=2) # 2 second timeout to check end of notifications272self.disconnect_nodes(0, 1)274# Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications275payment_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']276disconnect_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]277disconnect_cb = self.nodes[0].getblock(disconnect_block)["tx"][0]278assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex())279assert_equal(hashtx.receive().hex(), payment_txid)280assert_equal(hashtx.receive().hex(), disconnect_cb)282# Generate 2 blocks in nodes[1] to a different address to ensure split283connect_blocks = self.generatetoaddress(self.nodes[1], 2, ADDRESS_BCRT1_P2WSH_OP_TRUE, sync_fun=self.no_op)285# nodes[0] will reorg chain after connecting back nodes[1]286self.connect_nodes(0, 1)287self.sync_blocks() # tx in mempool valid but not advertised289# Should receive nodes[1] tip290assert_equal(self.nodes[1].getbestblockhash(), hashblock.receive().hex())292# During reorg:293# Get old payment transaction notification from disconnect and disconnected cb294assert_equal(hashtx.receive().hex(), payment_txid)295assert_equal(hashtx.receive().hex(), disconnect_cb)296# And the payment transaction again due to mempool entry297assert_equal(hashtx.receive().hex(), payment_txid)298assert_equal(hashtx.receive().hex(), payment_txid)299# And the new connected coinbases300for i in [0, 1]:301assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[i])["tx"][0])303# If we do a simple invalidate we announce the disconnected coinbase304self.nodes[0].invalidateblock(connect_blocks[1])305assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[1])["tx"][0])306# And the current tip307assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[0])["tx"][0])309def test_sequence(self):310"""311Sequence zmq notifications give every blockhash and txhash in order312of processing, regardless of IBD, re-orgs, etc.313Format of messages:314<32-byte hash>C : Blockhash connected315<32-byte hash>D : Blockhash disconnected316<32-byte hash>R<8-byte LE uint> : Transactionhash removed from mempool for non-block inclusion reason317<32-byte hash>A<8-byte LE uint> : Transactionhash added mempool318"""319self.log.info("Testing 'sequence' publisher")320[seq] = self.setup_zmq_test([("sequence", f"tcp://127.0.0.1:{self.zmq_port_base}")])321self.disconnect_nodes(0, 1)323# Mempool sequence number starts at 1324seq_num = 1326# Generate 1 block in nodes[0] and receive all notifications327dc_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]329# Note: We are not notified of any block transactions, coinbase or mined330assert_equal((self.nodes[0].getbestblockhash(), "C", None), seq.receive_sequence())332# Generate 2 blocks in nodes[1] to a different address to ensure a chain split333self.generatetoaddress(self.nodes[1], 2, ADDRESS_BCRT1_P2WSH_OP_TRUE, sync_fun=self.no_op)335# nodes[0] will reorg chain after connecting back nodes[1]336self.connect_nodes(0, 1)338# Then we receive all block (dis)connect notifications for the 2 block reorg339assert_equal((dc_block, "D", None), seq.receive_sequence())340block_count = self.nodes[1].getblockcount()341assert_equal((self.nodes[1].getblockhash(block_count-1), "C", None), seq.receive_sequence())342assert_equal((self.nodes[1].getblockhash(block_count), "C", None), seq.receive_sequence())344self.log.info("Wait for tx from second node")345payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1])346payment_txid = payment_tx['txid']347self.sync_all()348self.log.info("Testing sequence notifications with mempool sequence values")350# Should receive the broadcasted txid.351assert_equal((payment_txid, "A", seq_num), seq.receive_sequence())352seq_num += 1354self.log.info("Testing RBF notification")355# Replace it to test eviction/addition notification356payment_tx['tx'].vout[0].nValue -= 1000357rbf_txid = self.nodes[1].sendrawtransaction(payment_tx['tx'].serialize().hex())358self.sync_all()359assert_equal((payment_txid, "R", seq_num), seq.receive_sequence())360seq_num += 1361assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence())362seq_num += 1364# Doesn't get published when mined, make a block and tx to "flush" the possibility365# though the mempool sequence number does go up by the number of transactions366# removed from the mempool by the block mining it.367mempool_size = len(self.nodes[0].getrawmempool())368c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0]369# Make sure the number of mined transactions matches the number of txs out of mempool370mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool())371assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta)372seq_num += mempool_size_delta373payment_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[1])['txid']374self.sync_all()375assert_equal((c_block, "C", None), seq.receive_sequence())376assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence())377seq_num += 1379# Spot check getrawmempool results that they only show up when asked for380assert type(self.nodes[0].getrawmempool()) is list381assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list382assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True)383assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True)384assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num)386self.log.info("Testing reorg notifications")387# Manually invalidate the last block to test mempool re-entry388# N.B. This part could be made more lenient in exact ordering389# since it greatly depends on inner-workings of blocks/mempool390# during "deep" re-orgs. Probably should "re-construct"391# blockchain/mempool state from notifications instead.392block_count = self.nodes[0].getblockcount()393best_hash = self.nodes[0].getbestblockhash()394self.nodes[0].invalidateblock(best_hash)396# Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective397# of the time they were gathered.398ensure_for(duration=2, f=lambda: self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num)400assert_equal((best_hash, "D", None), seq.receive_sequence())401assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence())402seq_num += 1404# Other things may happen but aren't wallet-deterministic so we don't test for them currently405self.nodes[0].reconsiderblock(best_hash)406self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE)408self.log.info("Evict mempool transaction by block conflict")409orig_tx = self.wallet.send_self_transfer(from_node=self.nodes[0])410orig_txid = orig_tx['txid']412# More to be simply mined413more_tx = []414for _ in range(5):415more_tx.append(self.wallet.send_self_transfer(from_node=self.nodes[0]))417orig_tx['tx'].vout[0].nValue -= 1000418bump_txid = self.nodes[0].sendrawtransaction(orig_tx['tx'].serialize().hex())419# Mine the pre-bump tx420txs_to_add = [orig_tx['hex']] + [tx['hex'] for tx in more_tx]421block = create_block(int(self.nodes[0].getbestblockhash(), 16), height=self.nodes[0].getblockcount() + 1, txlist=txs_to_add)422add_witness_commitment(block)423block.solve()424assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)425tip = self.nodes[0].getbestblockhash()426assert_equal(int(tip, 16), block.hash_int)427orig_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']429# Flush old notifications until evicted tx original entry430(hash_str, label, mempool_seq) = seq.receive_sequence()431while hash_str != orig_txid:432(hash_str, label, mempool_seq) = seq.receive_sequence()433mempool_seq += 1435# Added original tx436assert_equal(label, "A")437# More transactions to be simply mined438for i in range(len(more_tx)):439assert_equal((more_tx[i]['txid'], "A", mempool_seq), seq.receive_sequence())440mempool_seq += 1441# Bumped by rbf442assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())443mempool_seq += 1444assert_equal((bump_txid, "A", mempool_seq), seq.receive_sequence())445mempool_seq += 1446# Conflict announced first, then block447assert_equal((bump_txid, "R", mempool_seq), seq.receive_sequence())448mempool_seq += 1449assert_equal((tip, "C", None), seq.receive_sequence())450mempool_seq += len(more_tx)451# Last tx452assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())453mempool_seq += 1454self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)455self.sync_all() # want to make sure we didn't break "consensus" for other tests457def test_mempool_sync(self):458"""459Use sequence notification plus getrawmempool sequence results to "sync mempool"460"""462self.log.info("Testing 'mempool sync' usage of sequence notifier")463[seq] = self.setup_zmq_test([("sequence", f"tcp://127.0.0.1:{self.zmq_port_base}")])465# In-memory counter, should always start at 1466next_mempool_seq = self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"]467assert_equal(next_mempool_seq, 1)469# Some transactions have been happening but we aren't consuming zmq notifications yet470# or we lost a ZMQ message somehow and want to start over471txs = []472num_txs = 5473for _ in range(num_txs):474txs.append(self.wallet.send_self_transfer(from_node=self.nodes[1]))475self.sync_all()477# 1) Consume backlog until we get a mempool sequence number478(hash_str, label, zmq_mem_seq) = seq.receive_sequence()479while zmq_mem_seq is None:480(hash_str, label, zmq_mem_seq) = seq.receive_sequence()482assert label in ("A", "R")483assert hash_str is not None485# 2) We need to "seed" our view of the mempool486mempool_snapshot = self.nodes[0].getrawmempool(mempool_sequence=True)487mempool_view = set(mempool_snapshot["txids"])488get_raw_seq = mempool_snapshot["mempool_sequence"]489assert_equal(get_raw_seq, num_txs + 1)490assert zmq_mem_seq < get_raw_seq492# Things continue to happen in the "interim" while waiting for snapshot results493# We have node 0 do all these to avoid p2p races with RBF announcements494for _ in range(num_txs):495txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0]))496txs[-1]['tx'].vout[0].nValue -= 1000497self.nodes[0].sendrawtransaction(txs[-1]['tx'].serialize().hex())498self.sync_all()499self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)500final_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.