Changeset View
Changeset View
Standalone View
Standalone View
test/functional/mempool_reorg.py
Show All 12 Lines | |||||
from test_framework.wallet import MiniWallet | from test_framework.wallet import MiniWallet | ||||
class MempoolCoinbaseTest(BitcoinTestFramework): | class MempoolCoinbaseTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [ | self.extra_args = [ | ||||
# immediate tx relay | # immediate tx relay | ||||
['-whitelist=noban@127.0.0.1', ], | [ | ||||
[] | "-whitelist=noban@127.0.0.1", | ||||
], | |||||
[], | |||||
] | ] | ||||
def run_test(self): | def run_test(self): | ||||
wallet = MiniWallet(self.nodes[0]) | wallet = MiniWallet(self.nodes[0]) | ||||
# Start with a 200 block chain | # Start with a 200 block chain | ||||
assert_equal(self.nodes[0].getblockcount(), 200) | assert_equal(self.nodes[0].getblockcount(), 200) | ||||
self.log.info("Add 4 coinbase utxos to the miniwallet") | self.log.info("Add 4 coinbase utxos to the miniwallet") | ||||
# Block 76 contains the first spendable coinbase txs. | # Block 76 contains the first spendable coinbase txs. | ||||
first_block = 76 | first_block = 76 | ||||
wallet.rescan_utxos() | wallet.rescan_utxos() | ||||
# Three scenarios for re-orging coinbase spends in the memory pool: | # Three scenarios for re-orging coinbase spends in the memory pool: | ||||
# 1. Direct coinbase spend : spend_1 | # 1. Direct coinbase spend : spend_1 | ||||
# 2. Indirect (coinbase spend in chain, child in mempool) : spend_2 and spend_2_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 | # 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 invalidateblock to make all of the above coinbase spends invalid (immature coinbase), | ||||
# and make sure the mempool code behaves correctly. | # and make sure the mempool code behaves correctly. | ||||
b = [ | b = [self.nodes[0].getblockhash(n) for n in range(first_block, first_block + 4)] | ||||
self.nodes[0].getblockhash(n) for n in range( | coinbase_txids = [self.nodes[0].getblock(h)["tx"][0] for h in b] | ||||
first_block, | |||||
first_block + 4)] | |||||
coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] | |||||
utxo_1 = wallet.get_utxo(txid=coinbase_txids[1]) | utxo_1 = wallet.get_utxo(txid=coinbase_txids[1]) | ||||
utxo_2 = wallet.get_utxo(txid=coinbase_txids[2]) | utxo_2 = wallet.get_utxo(txid=coinbase_txids[2]) | ||||
utxo_3 = wallet.get_utxo(txid=coinbase_txids[3]) | utxo_3 = wallet.get_utxo(txid=coinbase_txids[3]) | ||||
self.log.info( | self.log.info( | ||||
"Create three transactions spending from coinbase utxos: spend_1, spend_2, spend_3") | "Create three transactions spending from coinbase utxos: spend_1, spend_2," | ||||
" spend_3" | |||||
) | |||||
spend_1 = wallet.create_self_transfer( | spend_1 = wallet.create_self_transfer( | ||||
from_node=self.nodes[0], utxo_to_spend=utxo_1) | from_node=self.nodes[0], utxo_to_spend=utxo_1 | ||||
) | |||||
spend_2 = wallet.create_self_transfer( | spend_2 = wallet.create_self_transfer( | ||||
from_node=self.nodes[0], utxo_to_spend=utxo_2) | from_node=self.nodes[0], utxo_to_spend=utxo_2 | ||||
) | |||||
spend_3 = wallet.create_self_transfer( | spend_3 = wallet.create_self_transfer( | ||||
from_node=self.nodes[0], utxo_to_spend=utxo_3) | from_node=self.nodes[0], utxo_to_spend=utxo_3 | ||||
) | |||||
self.log.info( | self.log.info( | ||||
"Create another transaction which is time-locked to two blocks in the future") | "Create another transaction which is time-locked to two blocks in the" | ||||
" future" | |||||
) | |||||
utxo = wallet.get_utxo(txid=coinbase_txids[0]) | utxo = wallet.get_utxo(txid=coinbase_txids[0]) | ||||
timelock_tx = wallet.create_self_transfer( | timelock_tx = wallet.create_self_transfer( | ||||
from_node=self.nodes[0], | from_node=self.nodes[0], | ||||
utxo_to_spend=utxo, | utxo_to_spend=utxo, | ||||
locktime=self.nodes[0].getblockcount() + 2, | locktime=self.nodes[0].getblockcount() + 2, | ||||
)['hex'] | )["hex"] | ||||
self.log.info( | self.log.info("Check that the time-locked transaction is too immature to spend") | ||||
"Check that the time-locked transaction is too immature to spend") | assert_raises_rpc_error( | ||||
assert_raises_rpc_error(-26, | -26, "non-final", self.nodes[0].sendrawtransaction, timelock_tx | ||||
"non-final", | ) | ||||
self.nodes[0].sendrawtransaction, | |||||
timelock_tx) | |||||
self.log.info("Broadcast and mine spend_2 and spend_3") | self.log.info("Broadcast and mine spend_2 and spend_3") | ||||
wallet.sendrawtransaction( | wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_2["hex"]) | ||||
from_node=self.nodes[0], | wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_3["hex"]) | ||||
tx_hex=spend_2['hex']) | |||||
wallet.sendrawtransaction( | |||||
from_node=self.nodes[0], | |||||
tx_hex=spend_3['hex']) | |||||
self.log.info("Generate a block") | self.log.info("Generate a block") | ||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
self.log.info( | self.log.info( | ||||
"Check that time-locked transaction is still too immature to spend") | "Check that time-locked transaction is still too immature to spend" | ||||
assert_raises_rpc_error(-26, | ) | ||||
'non-final', | assert_raises_rpc_error( | ||||
self.nodes[0].sendrawtransaction, | -26, "non-final", self.nodes[0].sendrawtransaction, timelock_tx | ||||
timelock_tx) | ) | ||||
self.log.info("Create spend_2_1 and spend_3_1") | self.log.info("Create spend_2_1 and spend_3_1") | ||||
spend_2_1 = wallet.create_self_transfer( | spend_2_1 = wallet.create_self_transfer( | ||||
from_node=self.nodes[0], | from_node=self.nodes[0], utxo_to_spend=spend_2["new_utxo"] | ||||
utxo_to_spend=spend_2["new_utxo"]) | ) | ||||
spend_3_1 = wallet.create_self_transfer( | spend_3_1 = wallet.create_self_transfer( | ||||
from_node=self.nodes[0], | from_node=self.nodes[0], utxo_to_spend=spend_3["new_utxo"] | ||||
utxo_to_spend=spend_3["new_utxo"]) | ) | ||||
self.log.info("Broadcast and mine spend_3_1") | self.log.info("Broadcast and mine spend_3_1") | ||||
spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex']) | spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1["hex"]) | ||||
self.log.info("Generate a block") | self.log.info("Generate a block") | ||||
last_block = self.generate(self.nodes[0], 1) | last_block = self.generate(self.nodes[0], 1) | ||||
# generate() implicitly syncs blocks, so that peer 1 gets the block | # generate() implicitly syncs blocks, so that peer 1 gets the block | ||||
# before timelock_tx. | # before timelock_tx. | ||||
# Otherwise, peer 1 would put the timelock_tx in m_recent_rejects | # Otherwise, peer 1 would put the timelock_tx in m_recent_rejects | ||||
self.log.info("The time-locked transaction can now be spent") | self.log.info("The time-locked transaction can now be spent") | ||||
timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx) | timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx) | ||||
self.log.info("Add spend_1 and spend_2_1 to the mempool") | self.log.info("Add spend_1 and spend_2_1 to the mempool") | ||||
spend_1_id = self.nodes[0].sendrawtransaction(spend_1['hex']) | spend_1_id = self.nodes[0].sendrawtransaction(spend_1["hex"]) | ||||
spend_2_1_id = self.nodes[0].sendrawtransaction(spend_2_1['hex']) | spend_2_1_id = self.nodes[0].sendrawtransaction(spend_2_1["hex"]) | ||||
assert_equal(set(self.nodes[0].getrawmempool()), { | assert_equal( | ||||
spend_1_id, spend_2_1_id, timelock_tx_id}) | set(self.nodes[0].getrawmempool()), | ||||
{spend_1_id, spend_2_1_id, timelock_tx_id}, | |||||
) | |||||
self.sync_all() | self.sync_all() | ||||
# save acceptance heights of 2 of the txs to later test that they are | # save acceptance heights of 2 of the txs to later test that they are | ||||
# preserved across reorgs | # preserved across reorgs | ||||
spend_1_height = self.nodes[0].getmempoolentry(spend_1_id)["height"] | spend_1_height = self.nodes[0].getmempoolentry(spend_1_id)["height"] | ||||
spend_2_1_height = self.nodes[0].getmempoolentry(spend_2_1_id)[ | spend_2_1_height = self.nodes[0].getmempoolentry(spend_2_1_id)["height"] | ||||
"height"] | |||||
self.log.info("invalidate the last block") | self.log.info("invalidate the last block") | ||||
for node in self.nodes: | for node in self.nodes: | ||||
node.invalidateblock(last_block[0]) | node.invalidateblock(last_block[0]) | ||||
self.log.info( | self.log.info( | ||||
"The time-locked transaction is now too immature and has been removed from the mempool") | "The time-locked transaction is now too immature and has been removed from" | ||||
" the mempool" | |||||
) | |||||
self.log.info( | self.log.info( | ||||
"spend_3_1 has been re-orged out of the chain and is back in the mempool") | "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}) | assert_equal( | ||||
set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, spend_3_1_id} | |||||
) | |||||
# now ensure that the acceptance height of the two txs was preserved | # now ensure that the acceptance height of the two txs was preserved | ||||
# across reorgs (and is not the same as the current tip height) | # across reorgs (and is not the same as the current tip height) | ||||
tip_height = self.nodes[0].getblockchaininfo()["blocks"] | tip_height = self.nodes[0].getblockchaininfo()["blocks"] | ||||
assert spend_1_height != tip_height | assert spend_1_height != tip_height | ||||
assert spend_2_1_height != tip_height | assert spend_2_1_height != tip_height | ||||
assert_equal( | assert_equal( | ||||
spend_1_height, self.nodes[0].getmempoolentry(spend_1_id)["height"]) | spend_1_height, self.nodes[0].getmempoolentry(spend_1_id)["height"] | ||||
) | |||||
assert_equal( | assert_equal( | ||||
spend_2_1_height, self.nodes[0].getmempoolentry(spend_2_1_id)["height"]) | spend_2_1_height, self.nodes[0].getmempoolentry(spend_2_1_id)["height"] | ||||
) | |||||
# The new resurrected tx should just have height equal to current tip | # The new resurrected tx should just have height equal to current tip | ||||
# height | # height | ||||
assert_equal( | assert_equal(tip_height, self.nodes[0].getmempoolentry(spend_3_1_id)["height"]) | ||||
tip_height, self.nodes[0].getmempoolentry(spend_3_1_id)["height"]) | |||||
self.log.info( | self.log.info( | ||||
"Use invalidateblock to re-org back and make all those coinbase spends immature/invalid") | "Use invalidateblock to re-org back and make all those coinbase spends" | ||||
" immature/invalid" | |||||
) | |||||
b = self.nodes[0].getblockhash(first_block + 100) | b = self.nodes[0].getblockhash(first_block + 100) | ||||
for node in self.nodes: | for node in self.nodes: | ||||
node.invalidateblock(b) | node.invalidateblock(b) | ||||
self.log.info("Check that the mempool is empty") | self.log.info("Check that the mempool is empty") | ||||
assert_equal(set(self.nodes[0].getrawmempool()), set()) | assert_equal(set(self.nodes[0].getrawmempool()), set()) | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
MempoolCoinbaseTest().main() | MempoolCoinbaseTest().main() |