wallet_txn_doublespend.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2014-2022 The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test the wallet accounts properly when there is a double-spend conflict.""" 6 from decimal import Decimal 7 8 from test_framework.test_framework import BitcoinTestFramework 9 from test_framework.util import ( 10 assert_equal, 11 ) 12 13 14 class TxnMallTest(BitcoinTestFramework): 15 def set_test_params(self): 16 self.num_nodes = 3 17 self.supports_cli = False 18 19 def skip_test_if_missing_module(self): 20 self.skip_if_no_wallet() 21 22 def add_options(self, parser): 23 self.add_wallet_options(parser) 24 parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true", 25 help="Test double-spend of 1-confirmed transaction") 26 27 def setup_network(self): 28 # Start with split network: 29 super().setup_network() 30 self.disconnect_nodes(1, 2) 31 32 def spend_utxo(self, utxo, outputs): 33 inputs = [utxo] 34 tx = self.nodes[0].createrawtransaction(inputs, outputs) 35 tx = self.nodes[0].fundrawtransaction(tx) 36 tx = self.nodes[0].signrawtransactionwithwallet(tx['hex']) 37 return self.nodes[0].sendrawtransaction(tx['hex']) 38 39 def run_test(self): 40 # All nodes should start with 1,250 BTC: 41 starting_balance = 1250 42 43 # All nodes should be out of IBD. 44 # If the nodes are not all out of IBD, that can interfere with 45 # blockchain sync later in the test when nodes are connected, due to 46 # timing issues. 47 for n in self.nodes: 48 assert n.getblockchaininfo()["initialblockdownload"] == False 49 50 for i in range(3): 51 assert_equal(self.nodes[i].getbalance(), starting_balance) 52 53 # Assign coins to foo and bar addresses: 54 node0_address_foo = self.nodes[0].getnewaddress() 55 fund_foo_utxo = self.create_outpoints(self.nodes[0], outputs=[{node0_address_foo: 1219}])[0] 56 fund_foo_tx = self.nodes[0].gettransaction(fund_foo_utxo['txid']) 57 self.nodes[0].lockunspent(False, [fund_foo_utxo]) 58 59 node0_address_bar = self.nodes[0].getnewaddress() 60 fund_bar_utxo = self.create_outpoints(node=self.nodes[0], outputs=[{node0_address_bar: 29}])[0] 61 fund_bar_tx = self.nodes[0].gettransaction(fund_bar_utxo['txid']) 62 63 assert_equal(self.nodes[0].getbalance(), 64 starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]) 65 66 # Coins are sent to node1_address 67 node1_address = self.nodes[1].getnewaddress() 68 69 # First: use raw transaction API to send 1240 BTC to node1_address, 70 # but don't broadcast: 71 doublespend_fee = Decimal('-.02') 72 inputs = [fund_foo_utxo, fund_bar_utxo] 73 change_address = self.nodes[0].getnewaddress() 74 outputs = {} 75 outputs[node1_address] = 1240 76 outputs[change_address] = 1248 - 1240 + doublespend_fee 77 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) 78 doublespend = self.nodes[0].signrawtransactionwithwallet(rawtx) 79 assert_equal(doublespend["complete"], True) 80 81 # Create two spends using 1 50 BTC coin each 82 txid1 = self.spend_utxo(fund_foo_utxo, {node1_address: 40}) 83 txid2 = self.spend_utxo(fund_bar_utxo, {node1_address: 20}) 84 85 # Have node0 mine a block: 86 if (self.options.mine_block): 87 self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:2])) 88 89 tx1 = self.nodes[0].gettransaction(txid1) 90 tx2 = self.nodes[0].gettransaction(txid2) 91 92 # Node0's balance should be starting balance, plus 50BTC for another 93 # matured block, minus 40, minus 20, and minus transaction fees: 94 expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"] 95 if self.options.mine_block: 96 expected += 50 97 expected += tx1["amount"] + tx1["fee"] 98 expected += tx2["amount"] + tx2["fee"] 99 assert_equal(self.nodes[0].getbalance(), expected) 100 101 if self.options.mine_block: 102 assert_equal(tx1["confirmations"], 1) 103 assert_equal(tx2["confirmations"], 1) 104 # Node1's balance should be both transaction amounts: 105 assert_equal(self.nodes[1].getbalance(), starting_balance - tx1["amount"] - tx2["amount"]) 106 else: 107 assert_equal(tx1["confirmations"], 0) 108 assert_equal(tx2["confirmations"], 0) 109 110 # Now give doublespend and its parents to miner: 111 self.nodes[2].sendrawtransaction(fund_foo_tx["hex"]) 112 self.nodes[2].sendrawtransaction(fund_bar_tx["hex"]) 113 doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"]) 114 # ... mine a block... 115 self.generate(self.nodes[2], 1, sync_fun=self.no_op) 116 117 # Reconnect the split network, and sync chain: 118 self.connect_nodes(1, 2) 119 self.generate(self.nodes[2], 1) # Mine another block to make sure we sync 120 assert_equal(self.nodes[0].gettransaction(doublespend_txid)["confirmations"], 2) 121 122 # Re-fetch transaction info: 123 tx1 = self.nodes[0].gettransaction(txid1) 124 tx2 = self.nodes[0].gettransaction(txid2) 125 126 # Both transactions should be conflicted 127 assert_equal(tx1["confirmations"], -2) 128 assert_equal(tx2["confirmations"], -2) 129 130 # Node0's total balance should be starting balance, plus 100BTC for 131 # two more matured blocks, minus 1240 for the double-spend, plus fees (which are 132 # negative): 133 expected = starting_balance + 100 - 1240 + fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee 134 assert_equal(self.nodes[0].getbalance(), expected) 135 136 # Node1's balance should be its initial balance (1250 for 25 block rewards) plus the doublespend: 137 assert_equal(self.nodes[1].getbalance(), 1250 + 1240) 138 139 140 if __name__ == '__main__': 141 TxnMallTest().main()