mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 23:03:45 +01:00
Merge bitcoin/bitcoin#32587: test: Fix reorg patterns in tests to use proper fork-based approach
70d9e8f0a1fix: reorg behaviour in mempool tests to match real one (yuvicc)540ed333f6Move the create_empty_fork method to the test framework's blocktools.py module to enable reuse across multiple tests. (yuvicc) Pull request description: Updated functional tests to replace direct use of `invalidateblock` with proper fork-based reorg behaviour. The direct invalidation approach bypasses important validation checks and has depth limitations(10 block) that don't match real-world reorg scenarios. For more details see #32531. Fixes #32531 ACKs for top commit: instagibbs: reACK70d9e8f0a1theStack: re-ACK70d9e8f0a1Tree-SHA512: 8aae298bfa295b4e0e4627b522e9eac549399008fd8e336a66f8c9950c886917da0b3f0bdc62d0c8ea2b8082f36639300cac4070986a7766398e15bc1f666da5
This commit is contained in:
@@ -20,6 +20,9 @@ from test_framework.util import (
|
||||
from test_framework.wallet import (
|
||||
MiniWallet,
|
||||
)
|
||||
from test_framework.blocktools import (
|
||||
create_empty_fork
|
||||
)
|
||||
|
||||
class EphemeralDustTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@@ -64,6 +67,12 @@ class EphemeralDustTest(BitcoinTestFramework):
|
||||
|
||||
return dusty_tx, sweep_tx
|
||||
|
||||
def trigger_reorg(self, fork_blocks):
|
||||
"""Trigger reorg of the fork blocks."""
|
||||
for block in fork_blocks:
|
||||
self.nodes[0].submitblock(block.serialize().hex())
|
||||
assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
|
||||
|
||||
def run_test(self):
|
||||
|
||||
node = self.nodes[0]
|
||||
@@ -323,11 +332,15 @@ class EphemeralDustTest(BitcoinTestFramework):
|
||||
|
||||
# Get dusty tx mined, then check that it makes it back into mempool on reorg
|
||||
# due to bypass_limits allowing 0-fee individually
|
||||
|
||||
# Prep for fork with empty blocks
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3)
|
||||
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, dusty_tx["hex"])
|
||||
|
||||
block_res = self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"]], sync_fun=self.no_op)
|
||||
self.nodes[0].invalidateblock(block_res["hash"])
|
||||
self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"]], sync_fun=self.no_op)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"]], sync=False)
|
||||
|
||||
# Create a sweep that has dust of its own and leaves dusty_tx's dust unspent
|
||||
@@ -335,9 +348,12 @@ class EphemeralDustTest(BitcoinTestFramework):
|
||||
self.add_output_to_create_multi_result(sweep_tx)
|
||||
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, sweep_tx["hex"])
|
||||
|
||||
# Prep for fork with empty blocks
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
# Mine the sweep then re-org, the sweep will not make it back in due to spend checks
|
||||
block_res = self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"], sweep_tx["hex"]], sync_fun=self.no_op)
|
||||
self.nodes[0].invalidateblock(block_res["hash"])
|
||||
self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"], sweep_tx["hex"]], sync_fun=self.no_op)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"]], sync=False)
|
||||
|
||||
# Should re-enter if dust is swept
|
||||
@@ -345,27 +361,39 @@ class EphemeralDustTest(BitcoinTestFramework):
|
||||
self.add_output_to_create_multi_result(sweep_tx_2)
|
||||
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, sweep_tx_2["hex"])
|
||||
|
||||
reconsider_block_res = self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"], sweep_tx_2["hex"]], sync_fun=self.no_op)
|
||||
self.nodes[0].invalidateblock(reconsider_block_res["hash"])
|
||||
# Prep for fork with empty blocks
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"], sweep_tx_2["hex"]], sync_fun=self.no_op)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"], sweep_tx_2["tx"]], sync=False)
|
||||
|
||||
# TRUC transactions restriction for ephemeral dust disallows further spends of ancestor chains
|
||||
child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=sweep_tx_2["new_utxos"], version=3)
|
||||
assert_raises_rpc_error(-26, "TRUC-violation", self.nodes[0].sendrawtransaction, child_tx["hex"])
|
||||
|
||||
self.nodes[0].reconsiderblock(reconsider_block_res["hash"])
|
||||
# Clean up the mempool
|
||||
self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_tx["hex"], sweep_tx_2["hex"]], sync_fun=self.no_op)
|
||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||
|
||||
self.log.info("Test that ephemeral dust tx with fees or multi dust don't enter mempool via reorg")
|
||||
multi_dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, num_dust_outputs=2)
|
||||
block_res = self.generateblock(self.nodes[0], self.wallet.get_address(), [multi_dusty_tx["hex"]], sync_fun=self.no_op)
|
||||
self.nodes[0].invalidateblock(block_res["hash"])
|
||||
|
||||
# Prep for fork with empty blocks
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
self.generateblock(self.nodes[0], self.wallet.get_address(), [multi_dusty_tx["hex"]], sync_fun=self.no_op)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||
|
||||
# With fee and one dust
|
||||
dusty_fee_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_tx_fee=1)
|
||||
block_res = self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_fee_tx["hex"]], sync_fun=self.no_op)
|
||||
self.nodes[0].invalidateblock(block_res["hash"])
|
||||
|
||||
# Prep for fork with empty blocks
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
self.generateblock(self.nodes[0], self.wallet.get_address(), [dusty_fee_tx["hex"]], sync_fun=self.no_op)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||
|
||||
# Re-connect and make sure we have same state still
|
||||
|
||||
@@ -15,6 +15,7 @@ from test_framework.util import (
|
||||
assert_equal,
|
||||
)
|
||||
from test_framework.wallet import MiniWallet
|
||||
from test_framework.blocktools import create_empty_fork
|
||||
|
||||
# custom limits for node1
|
||||
CUSTOM_CLUSTER_LIMIT = 10
|
||||
@@ -33,6 +34,12 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||
],
|
||||
]
|
||||
|
||||
def trigger_reorg(self, fork_blocks, node):
|
||||
"""Trigger reorg of the fork blocks."""
|
||||
for block in fork_blocks:
|
||||
node.submitblock(block.serialize().hex())
|
||||
assert_equal(node.getbestblockhash(), fork_blocks[-1].hash_hex)
|
||||
|
||||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
self.wallet.rescan_utxos()
|
||||
@@ -227,9 +234,16 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||
|
||||
# Test reorg handling
|
||||
# First, the basics:
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
mempool0 = self.nodes[0].getrawmempool(False)
|
||||
self.generate(self.nodes[0], 1)
|
||||
self.trigger_reorg(fork_blocks, self.nodes[0])
|
||||
|
||||
# Check if the txs are returned to the mempool
|
||||
assert_equal(self.nodes[0].getrawmempool(), mempool0)
|
||||
|
||||
# Clean-up the mempool
|
||||
self.generate(self.nodes[0], 1)
|
||||
self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash())
|
||||
self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash())
|
||||
|
||||
# Now test the case where node1 has a transaction T in its mempool that
|
||||
# depends on transactions A and B which are in a mined block, and the
|
||||
@@ -244,6 +258,9 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||
# Tx1 and Tx7, and add to node1's mempool, then disconnect the
|
||||
# last block.
|
||||
|
||||
# Prep for fork
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
# Create tx0 with 2 outputs
|
||||
tx0 = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=2)
|
||||
|
||||
@@ -261,8 +278,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||
self.sync_mempools()
|
||||
|
||||
# Now try to disconnect the tip on each node...
|
||||
self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
|
||||
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
|
||||
self.trigger_reorg(fork_blocks, self.nodes[0])
|
||||
self.sync_blocks()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -22,6 +22,13 @@ from test_framework.p2p import (
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||
from test_framework.wallet import MiniWallet
|
||||
from test_framework.blocktools import (
|
||||
create_empty_fork,
|
||||
)
|
||||
|
||||
# Number of blocks to create in temporary blockchain branch for reorg testing
|
||||
# needs to be long enough to allow MTP to move arbitrarily forward
|
||||
FORK_LENGTH = 20
|
||||
|
||||
class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@@ -33,6 +40,12 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
[]
|
||||
]
|
||||
|
||||
def trigger_reorg(self, fork_blocks, node):
|
||||
"""Trigger reorg of the fork blocks."""
|
||||
for block in fork_blocks:
|
||||
node.submitblock(block.serialize().hex())
|
||||
assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
|
||||
|
||||
def test_reorg_relay(self):
|
||||
self.log.info("Test that transactions from disconnected blocks are available for relay immediately")
|
||||
# Prevent time from moving forward
|
||||
@@ -112,6 +125,10 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
wallet = self.wallet
|
||||
|
||||
# Prevent clock from moving blocks further forward in time
|
||||
now = int(time.time())
|
||||
self.nodes[0].setmocktime(now)
|
||||
|
||||
# Start with a 200 block chain
|
||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||
|
||||
@@ -123,7 +140,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
# 1. Direct coinbase spend : spend_1
|
||||
# 2. Indirect (coinbase spend in chain, child in mempool) : spend_2 and spend_2_1
|
||||
# 3. Indirect (coinbase and child both in chain) : spend_3 and spend_3_1
|
||||
# Use invalidateblock to make all of the above coinbase spends invalid (immature coinbase),
|
||||
# Use re-org to make all of the above coinbase spends invalid (immature coinbase),
|
||||
# and make sure the mempool code behaves correctly.
|
||||
b = [self.nodes[0].getblockhash(n) for n in range(first_block, first_block+4)]
|
||||
coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b]
|
||||
@@ -135,11 +152,12 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
spend_2 = wallet.create_self_transfer(utxo_to_spend=utxo_2)
|
||||
spend_3 = wallet.create_self_transfer(utxo_to_spend=utxo_3)
|
||||
|
||||
self.log.info("Create another transaction which is time-locked to two blocks in the future")
|
||||
self.log.info("Create another transaction which is time-locked to 300 seconds in the future")
|
||||
future = now + 300
|
||||
utxo = wallet.get_utxo(txid=coinbase_txids[0])
|
||||
timelock_tx = wallet.create_self_transfer(
|
||||
utxo_to_spend=utxo,
|
||||
locktime=self.nodes[0].getblockcount() + 2,
|
||||
locktime=future,
|
||||
)['hex']
|
||||
|
||||
self.log.info("Check that the time-locked transaction is too immature to spend")
|
||||
@@ -160,10 +178,18 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
self.log.info("Broadcast and mine spend_3_1")
|
||||
spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex'])
|
||||
self.log.info("Generate a block")
|
||||
last_block = self.generate(self.nodes[0], 1)
|
||||
|
||||
# Prep for fork, only go FORK_LENGTH seconds into the MTP future max
|
||||
fork_blocks = create_empty_fork(self.nodes[0], fork_length=FORK_LENGTH)
|
||||
|
||||
# Jump node and MTP 300 seconds and generate a slightly weaker chain than reorg one
|
||||
self.nodes[0].setmocktime(future)
|
||||
self.generate(self.nodes[0], FORK_LENGTH - 1)
|
||||
block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
|
||||
assert(block_time >= now + 300)
|
||||
|
||||
# generate() implicitly syncs blocks, so that peer 1 gets the block before timelock_tx
|
||||
# Otherwise, peer 1 would put the timelock_tx in m_lazy_recent_rejects
|
||||
|
||||
self.log.info("The time-locked transaction can now be spent")
|
||||
timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx)
|
||||
|
||||
@@ -174,15 +200,22 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, timelock_tx_id})
|
||||
self.sync_all()
|
||||
|
||||
self.log.info("invalidate the last block")
|
||||
for node in self.nodes:
|
||||
node.invalidateblock(last_block[0])
|
||||
self.trigger_reorg(fork_blocks, self.nodes[0])
|
||||
self.sync_blocks()
|
||||
|
||||
# We went backwards in time to boot timelock_tx_id
|
||||
fork_block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
|
||||
assert fork_block_time < block_time
|
||||
|
||||
self.log.info("The time-locked transaction is now too immature and has been removed from the mempool")
|
||||
self.log.info("spend_3_1 has been re-orged out of the chain and is back in the mempool")
|
||||
assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, spend_3_1_id})
|
||||
|
||||
self.log.info("Use invalidateblock to re-org back and make all those coinbase spends immature/invalid")
|
||||
b = self.nodes[0].getblockhash(first_block + 100)
|
||||
|
||||
# Use invalidateblock to go backwards in MTP time.
|
||||
# invalidateblock actually moves MTP backwards, making timelock_tx_id valid again.
|
||||
for node in self.nodes:
|
||||
node.invalidateblock(b)
|
||||
|
||||
|
||||
@@ -7,12 +7,18 @@
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
from test_framework.wallet import MiniWallet
|
||||
|
||||
from test_framework.blocktools import create_empty_fork
|
||||
|
||||
class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
||||
def trigger_reorg(self, fork_blocks):
|
||||
"""Trigger reorg of the fork blocks."""
|
||||
for block in fork_blocks:
|
||||
self.nodes[0].submitblock(block.serialize().hex())
|
||||
assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
wallet = MiniWallet(node)
|
||||
@@ -26,6 +32,9 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
# ... make sure all the transactions are put back in the mempool
|
||||
# Mine a new block
|
||||
# ... make sure all the transactions are confirmed again
|
||||
|
||||
# Prep for fork
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
blocks = []
|
||||
spends1_ids = [wallet.send_self_transfer(from_node=node)['txid'] for _ in range(3)]
|
||||
blocks.extend(self.generate(node, 1))
|
||||
@@ -40,8 +49,8 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
# Checks that all spend txns are contained in the mined blocks
|
||||
assert spends_ids < confirmed_txns
|
||||
|
||||
# Use invalidateblock to re-org back
|
||||
node.invalidateblock(blocks[0])
|
||||
# Trigger reorg
|
||||
self.trigger_reorg(fork_blocks)
|
||||
|
||||
# All txns should be back in mempool with 0 confirmations
|
||||
assert_equal(set(node.getrawmempool()), spends_ids)
|
||||
|
||||
@@ -18,6 +18,9 @@ from test_framework.wallet import (
|
||||
DEFAULT_FEE,
|
||||
MiniWallet,
|
||||
)
|
||||
from test_framework.blocktools import (
|
||||
create_empty_fork,
|
||||
)
|
||||
|
||||
MAX_REPLACEMENT_CANDIDATES = 100
|
||||
TRUC_MAX_VSIZE = 10000
|
||||
@@ -50,6 +53,12 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
assert_equal(len(txids), len(mempool_contents))
|
||||
assert all([txid in txids for txid in mempool_contents])
|
||||
|
||||
def trigger_reorg(self, fork_blocks):
|
||||
"""Trigger reorg of the fork blocks."""
|
||||
for block in fork_blocks:
|
||||
self.nodes[0].submitblock(block.serialize().hex())
|
||||
assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
|
||||
|
||||
@cleanup()
|
||||
def test_truc_max_vsize(self):
|
||||
node = self.nodes[0]
|
||||
@@ -164,6 +173,9 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
@cleanup()
|
||||
def test_truc_reorg(self):
|
||||
node = self.nodes[0]
|
||||
|
||||
# Prep for fork
|
||||
fork_blocks = create_empty_fork(node)
|
||||
self.log.info("Test that, during a reorg, TRUC rules are not enforced")
|
||||
self.check_mempool([])
|
||||
|
||||
@@ -182,9 +194,10 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
tx_chain_3 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_2["new_utxo"], version=3)
|
||||
|
||||
tx_to_mine = [tx_v3_block["hex"], tx_v2_block["hex"], tx_v3_block2["hex"], tx_chain_1["hex"], tx_chain_2["hex"], tx_chain_3["hex"]]
|
||||
block = self.generateblock(node, output="raw(42)", transactions=tx_to_mine)
|
||||
self.generateblock(node, output="raw(42)", transactions=tx_to_mine)
|
||||
|
||||
self.check_mempool([])
|
||||
|
||||
tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)
|
||||
tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)
|
||||
tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_vsize=1250, version=3)
|
||||
@@ -193,7 +206,7 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_4["txid"]])
|
||||
|
||||
# Reorg should have all block transactions re-accepted, ignoring TRUC enforcement
|
||||
node.invalidateblock(block["hash"])
|
||||
self.trigger_reorg(fork_blocks)
|
||||
self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_1["txid"], tx_chain_2["txid"], tx_chain_3["txid"], tx_chain_4["txid"]])
|
||||
|
||||
@cleanup(extra_args=["-limitclustercount=1"])
|
||||
@@ -463,21 +476,20 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
node = self.nodes[0]
|
||||
self.log.info("Test that children of a TRUC transaction can be replaced individually, even if there are multiple due to reorg")
|
||||
|
||||
# Prep for fork
|
||||
fork_blocks = create_empty_fork(node)
|
||||
ancestor_tx = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
|
||||
self.check_mempool([ancestor_tx["txid"]])
|
||||
|
||||
block = self.generate(node, 1)[0]
|
||||
self.generate(node, 1)[0]
|
||||
self.check_mempool([])
|
||||
|
||||
child_1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0])
|
||||
child_2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][1])
|
||||
self.check_mempool([child_1["txid"], child_2["txid"]])
|
||||
|
||||
self.generate(node, 1)
|
||||
self.check_mempool([])
|
||||
|
||||
# Create a reorg, causing ancestor_tx to exceed the 1-child limit
|
||||
node.invalidateblock(block)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
self.check_mempool([ancestor_tx["txid"], child_1["txid"], child_2["txid"]])
|
||||
assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
|
||||
|
||||
@@ -562,10 +574,12 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
node = self.nodes[0]
|
||||
self.log.info("Test that sibling eviction is not allowed when multiple siblings exist")
|
||||
|
||||
# Prep for fork
|
||||
fork_blocks = create_empty_fork(node)
|
||||
tx_with_multi_children = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=3, version=3, confirmed_only=True)
|
||||
self.check_mempool([tx_with_multi_children["txid"]])
|
||||
|
||||
block_to_disconnect = self.generate(node, 1)[0]
|
||||
self.generate(node, 1)
|
||||
self.check_mempool([])
|
||||
|
||||
tx_with_sibling1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][0])
|
||||
@@ -573,7 +587,7 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||
self.check_mempool([tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
|
||||
|
||||
# Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.
|
||||
node.invalidateblock(block_to_disconnect)
|
||||
self.trigger_reorg(fork_blocks)
|
||||
self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
|
||||
assert_equal(node.getmempoolentry(tx_with_multi_children["txid"])["descendantcount"], 3)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2020-2022 The Bitcoin Core developers
|
||||
# Copyright (c) 2020-present The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test mempool descendants/ancestors information update.
|
||||
@@ -11,10 +11,7 @@ from decimal import Decimal
|
||||
from math import ceil
|
||||
import time
|
||||
|
||||
from test_framework.blocktools import (
|
||||
create_block,
|
||||
create_coinbase,
|
||||
)
|
||||
from test_framework.blocktools import create_empty_fork
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||
from test_framework.wallet import MiniWallet
|
||||
@@ -30,25 +27,11 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||
self.num_nodes = 1
|
||||
self.extra_args = [['-limitclustersize=1000']]
|
||||
|
||||
def create_empty_fork(self, fork_length):
|
||||
'''
|
||||
Creates a fork using first node's chaintip as the starting point.
|
||||
Returns a list of blocks to submit in order.
|
||||
'''
|
||||
tip = int(self.nodes[0].getbestblockhash(), 16)
|
||||
height = self.nodes[0].getblockcount()
|
||||
block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
|
||||
|
||||
blocks = []
|
||||
for _ in range(fork_length):
|
||||
block = create_block(tip, create_coinbase(height + 1), block_time)
|
||||
block.solve()
|
||||
blocks.append(block)
|
||||
tip = block.hash_int
|
||||
block_time += 1
|
||||
height += 1
|
||||
|
||||
return blocks
|
||||
def trigger_reorg(self, fork_blocks):
|
||||
"""Trigger reorg of the fork blocks."""
|
||||
for block in fork_blocks:
|
||||
self.nodes[0].submitblock(block.serialize().hex())
|
||||
assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
|
||||
|
||||
def transaction_graph_test(self, size, *, n_tx_to_mine, fee=100_000):
|
||||
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
|
||||
@@ -69,7 +52,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||
|
||||
# Prep for fork with empty blocks to not use invalidateblock directly
|
||||
# for reorg case. The rpc has different codepath
|
||||
fork_blocks = self.create_empty_fork(fork_length=7)
|
||||
fork_blocks = create_empty_fork(self.nodes[0], fork_length=7)
|
||||
|
||||
tx_id = []
|
||||
tx_size = []
|
||||
@@ -146,7 +129,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||
|
||||
# Set up empty fork blocks ahead of time, needs to be longer than full fork made later
|
||||
fork_blocks = self.create_empty_fork(fork_length=60)
|
||||
fork_blocks = create_empty_fork(self.nodes[0], fork_length=60)
|
||||
|
||||
large_std_txs = []
|
||||
# Add children to ensure they're recursively removed if disconnectpool trimming of parent occurs
|
||||
@@ -176,8 +159,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||
|
||||
# Reorg back before the first block in the series, should drop something
|
||||
# but not all, and any time parent is dropped, child is also removed
|
||||
for block in fork_blocks:
|
||||
self.nodes[0].submitblock(block.serialize().hex())
|
||||
self.trigger_reorg(fork_blocks=fork_blocks)
|
||||
mempool = self.nodes[0].getrawmempool()
|
||||
expected_parent_count = len(large_std_txs) - 2
|
||||
assert_equal(len(mempool), expected_parent_count * 2)
|
||||
@@ -195,7 +177,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||
|
||||
# Prep fork
|
||||
fork_blocks = self.create_empty_fork(fork_length=10)
|
||||
fork_blocks = create_empty_fork(self.nodes[0])
|
||||
|
||||
# Two higher than descendant count
|
||||
chain = wallet.create_self_transfer_chain(chain_length=DEFAULT_CLUSTER_LIMIT + 2)
|
||||
|
||||
@@ -84,6 +84,9 @@ assert_equal(uint256_from_compact(DIFF_4_N_BITS), DIFF_4_TARGET)
|
||||
# From BIP325
|
||||
SIGNET_HEADER = b"\xec\xc7\xda\xa2"
|
||||
|
||||
# Number of blocks to create in temporary blockchain branch for reorg testing
|
||||
FORK_LENGTH = 10
|
||||
|
||||
def nbits_str(nbits):
|
||||
return f"{nbits:08x}"
|
||||
|
||||
@@ -113,6 +116,26 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl
|
||||
block.hashMerkleRoot = block.calc_merkle_root()
|
||||
return block
|
||||
|
||||
def create_empty_fork(node, fork_length=FORK_LENGTH):
|
||||
'''
|
||||
Creates a fork using node's chaintip as the starting point.
|
||||
Returns a list of blocks to submit in order.
|
||||
'''
|
||||
tip = int(node.getbestblockhash(), 16)
|
||||
height = node.getblockcount()
|
||||
block_time = node.getblock(node.getbestblockhash())['time'] + 1
|
||||
|
||||
blocks = []
|
||||
for _ in range(fork_length):
|
||||
block = create_block(tip, create_coinbase(height + 1), block_time)
|
||||
block.solve()
|
||||
blocks.append(block)
|
||||
tip = block.hash_int
|
||||
block_time += 1
|
||||
height += 1
|
||||
|
||||
return blocks
|
||||
|
||||
def get_witness_script(witness_root, witness_nonce):
|
||||
witness_commitment = hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce))
|
||||
output_data = WITNESS_COMMITMENT_HEADER + witness_commitment
|
||||
|
||||
Reference in New Issue
Block a user