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