mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-02 01:04:43 +02:00
Merge bitcoin/bitcoin#31794: wallet: abandon orphan coinbase txs, and their descendants, during startup
e4dd5a351btest: wallet, abandon coinbase txs and their descendants during startup (furszy)474139aa9bwallet: abandon inactive coinbase tx and their descendants during startup (furszy) Pull request description: Since #26499, we mark coinbase transactions and their descendants as abandoned when a reorg arises through the "block disconnection" signal handler. However, this does not cover all scenarios; external wallets could contain coinbase transactions from blocks the node has not seen yet, or the user could have replaced the chain with an earlier or different version (one without the coinbase chain). This affects balance calculation as well as mempool rebroadcast (descendants shouldn't be relayed). Fix this by marking orphaned coinbase transactions and their descendants as abandoned during wallet startup. ACKs for top commit: achow101: ACKe4dd5a351brkrux: tACKe4dd5a351bmzumsande: Code Review ACKe4dd5a351bTree-SHA512: 461a43de7a6f5a580f2e6e3b56ec9bc92239cd45e850a2ff594ab5488dcd4a507f68fbbf550a33d7173b2add0de80de1e1b3841e1dfab0c95b284212d8ced08a
This commit is contained in:
@@ -19,6 +19,7 @@ import shutil
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error
|
||||
)
|
||||
|
||||
class ReorgsRestoreTest(BitcoinTestFramework):
|
||||
@@ -31,6 +32,65 @@ class ReorgsRestoreTest(BitcoinTestFramework):
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def test_coinbase_automatic_abandon_during_startup(self):
|
||||
##########################################################################################################
|
||||
# Verify the wallet marks coinbase transactions, and their descendants, as abandoned during startup when #
|
||||
# the block is no longer part of the best chain. #
|
||||
##########################################################################################################
|
||||
self.log.info("Test automatic coinbase abandonment during startup")
|
||||
# Test setup: Sync nodes for the coming test, ensuring both are at the same block, then disconnect them to
|
||||
# generate two competing chains. After disconnection, verify no other peer connection exists.
|
||||
self.connect_nodes(1, 0)
|
||||
self.sync_blocks(self.nodes[:2])
|
||||
self.disconnect_nodes(1, 0)
|
||||
assert all(len(node.getpeerinfo()) == 0 for node in self.nodes[:2])
|
||||
|
||||
# Create a new block in node0, coinbase going to wallet0
|
||||
self.nodes[0].createwallet(wallet_name="w0", load_on_startup=True)
|
||||
wallet0 = self.nodes[0].get_wallet_rpc("w0")
|
||||
self.generatetoaddress(self.nodes[0], 1, wallet0.getnewaddress(), sync_fun=self.no_op)
|
||||
node0_coinbase_tx_hash = wallet0.getblock(wallet0.getbestblockhash(), verbose=1)['tx'][0]
|
||||
|
||||
# Mine 100 blocks on top to mature the coinbase and create a descendant
|
||||
self.generate(self.nodes[0], 101, sync_fun=self.no_op)
|
||||
# Make descendant, send-to-self
|
||||
descendant_tx_id = wallet0.sendtoaddress(wallet0.getnewaddress(), 1)
|
||||
|
||||
# Verify balance
|
||||
wallet0.syncwithvalidationinterfacequeue()
|
||||
assert(wallet0.getbalances()['mine']['trusted'] > 0)
|
||||
|
||||
# Now create a fork in node1. This will be used to replace node0's chain later.
|
||||
self.nodes[1].createwallet(wallet_name="w1", load_on_startup=True)
|
||||
wallet1 = self.nodes[1].get_wallet_rpc("w1")
|
||||
self.generatetoaddress(self.nodes[1], 1, wallet1.getnewaddress(), sync_fun=self.no_op)
|
||||
wallet1.syncwithvalidationinterfacequeue()
|
||||
|
||||
# Verify both nodes are on a different chain
|
||||
block0_best_hash, block1_best_hash = wallet0.getbestblockhash(), wallet1.getbestblockhash()
|
||||
assert(block0_best_hash != block1_best_hash)
|
||||
|
||||
# Stop both nodes and replace node0 chain entirely for the node1 chain
|
||||
self.stop_nodes()
|
||||
for path in ["chainstate", "blocks"]:
|
||||
shutil.rmtree(self.nodes[0].chain_path / path)
|
||||
shutil.copytree(self.nodes[1].chain_path / path, self.nodes[0].chain_path / path)
|
||||
|
||||
# Start node0 and verify that now it has node1 chain and no info about its previous best block
|
||||
self.start_node(0)
|
||||
wallet0 = self.nodes[0].get_wallet_rpc("w0")
|
||||
assert_equal(wallet0.getbestblockhash(), block1_best_hash)
|
||||
assert_raises_rpc_error(-5, "Block not found", wallet0.getblock, block0_best_hash)
|
||||
|
||||
# Verify the coinbase tx was marked as abandoned and balance correctly computed
|
||||
tx_info = wallet0.gettransaction(node0_coinbase_tx_hash)['details'][0]
|
||||
assert_equal(tx_info['abandoned'], True)
|
||||
assert_equal(tx_info['category'], 'orphan')
|
||||
assert(wallet0.getbalances()['mine']['trusted'] == 0)
|
||||
# Verify the coinbase descendant was also marked as abandoned
|
||||
assert_equal(wallet0.gettransaction(descendant_tx_id)['details'][0]['abandoned'], True)
|
||||
|
||||
|
||||
def run_test(self):
|
||||
# Send a tx from which to conflict outputs later
|
||||
txid_conflict_from = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
|
||||
@@ -100,5 +160,9 @@ class ReorgsRestoreTest(BitcoinTestFramework):
|
||||
assert_equal(conflicted_after_reorg["confirmations"], 1)
|
||||
assert conflicting["blockhash"] != conflicted_after_reorg["blockhash"]
|
||||
|
||||
# Verify we mark coinbase txs, and their descendants, as abandoned during startup
|
||||
self.test_coinbase_automatic_abandon_during_startup()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ReorgsRestoreTest(__file__).main()
|
||||
|
||||
Reference in New Issue
Block a user