0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-02 09:46:52 -05:00

test: add more wallet conflicts assertions

verify wallet conflicts from the receiver's side
This commit is contained in:
S3RK 2022-01-20 10:13:03 +01:00
parent 3b98bf9c43
commit 3ee6d0788e

View file

@ -29,20 +29,25 @@ class AbandonConflictTest(BitcoinTestFramework):
self.skip_if_no_wallet() self.skip_if_no_wallet()
def run_test(self): def run_test(self):
# create two wallets to tests conflicts from both sender's and receiver's sides
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.nodes[0].createwallet(wallet_name="bob")
bob = self.nodes[0].get_wallet_rpc("bob")
self.generate(self.nodes[1], COINBASE_MATURITY) self.generate(self.nodes[1], COINBASE_MATURITY)
balance = self.nodes[0].getbalance() balance = alice.getbalance()
txA = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) txA = alice.sendtoaddress(alice.getnewaddress(), Decimal("10"))
txB = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) txB = alice.sendtoaddress(alice.getnewaddress(), Decimal("10"))
txC = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) txC = alice.sendtoaddress(alice.getnewaddress(), Decimal("10"))
self.sync_mempools() self.sync_mempools()
self.generate(self.nodes[1], 1) self.generate(self.nodes[1], 1)
# Can not abandon non-wallet transaction # Can not abandon non-wallet transaction
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: self.nodes[0].abandontransaction(txid='ff' * 32)) assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: alice.abandontransaction(txid='ff' * 32))
# Can not abandon confirmed transaction # Can not abandon confirmed transaction
assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: self.nodes[0].abandontransaction(txid=txA)) assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: alice.abandontransaction(txid=txA))
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert balance - newbalance < Decimal("0.001") #no more than fees lost assert balance - newbalance < Decimal("0.001") #no more than fees lost
balance = newbalance balance = newbalance
@ -50,9 +55,9 @@ class AbandonConflictTest(BitcoinTestFramework):
self.disconnect_nodes(0, 1) self.disconnect_nodes(0, 1)
# Identify the 10btc outputs # Identify the 10btc outputs
nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10")) nA = next(tx_out["vout"] for tx_out in alice.gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10"))
nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10")) nB = next(tx_out["vout"] for tx_out in alice.gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10"))
nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10")) nC = next(tx_out["vout"] for tx_out in alice.gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10"))
inputs = [] inputs = []
# spend 10btc outputs from txA and txB # spend 10btc outputs from txA and txB
@ -60,39 +65,40 @@ class AbandonConflictTest(BitcoinTestFramework):
inputs.append({"txid": txB, "vout": nB}) inputs.append({"txid": txB, "vout": nB})
outputs = {} outputs = {}
outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998") outputs[alice.getnewaddress()] = Decimal("14.99998")
outputs[self.nodes[1].getnewaddress()] = Decimal("5") outputs[bob.getnewaddress()] = Decimal("5")
signed = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) signed = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs))
txAB1 = self.nodes[0].sendrawtransaction(signed["hex"]) txAB1 = self.nodes[0].sendrawtransaction(signed["hex"])
# Identify the 14.99998btc output # Identify the 14.99998btc output
nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998")) nAB = next(tx_out["vout"] for tx_out in alice.gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998"))
#Create a child tx spending AB1 and C #Create a child tx spending AB1 and C
inputs = [] inputs = []
inputs.append({"txid": txAB1, "vout": nAB}) inputs.append({"txid": txAB1, "vout": nAB})
inputs.append({"txid": txC, "vout": nC}) inputs.append({"txid": txC, "vout": nC})
outputs = {} outputs = {}
outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996") outputs[alice.getnewaddress()] = Decimal("24.9996")
signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) signed2 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs))
txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"]) txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"])
# Create a child tx spending ABC2 # Create a child tx spending ABC2
signed3_change = Decimal("24.999") signed3_change = Decimal("24.999")
inputs = [{"txid": txABC2, "vout": 0}] inputs = [{"txid": txABC2, "vout": 0}]
outputs = {self.nodes[0].getnewaddress(): signed3_change} outputs = {alice.getnewaddress(): signed3_change}
signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) signed3 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs))
# note tx is never directly referenced, only abandoned as a child of the above # note tx is never directly referenced, only abandoned as a child of the above
self.nodes[0].sendrawtransaction(signed3["hex"]) self.nodes[0].sendrawtransaction(signed3["hex"])
# In mempool txs from self should increase balance from change # In mempool txs from self should increase balance from change
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance - Decimal("30") + signed3_change) assert_equal(newbalance, balance - Decimal("30") + signed3_change)
balance = newbalance balance = newbalance
# Restart the node with a higher min relay fee so the parent tx is no longer in mempool # Restart the node with a higher min relay fee so the parent tx is no longer in mempool
# TODO: redo with eviction # TODO: redo with eviction
self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
assert self.nodes[0].getmempoolinfo()['loaded'] assert self.nodes[0].getmempoolinfo()['loaded']
# Verify txs no longer in either node's mempool # Verify txs no longer in either node's mempool
@ -101,25 +107,25 @@ class AbandonConflictTest(BitcoinTestFramework):
# Not in mempool txs from self should only reduce balance # Not in mempool txs from self should only reduce balance
# inputs are still spent, but change not received # inputs are still spent, but change not received
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance - signed3_change) assert_equal(newbalance, balance - signed3_change)
# Unconfirmed received funds that are not in mempool, also shouldn't show # Unconfirmed received funds that are not in mempool, also shouldn't show
# up in unconfirmed balance # up in unconfirmed balance
balances = self.nodes[0].getbalances()['mine'] balances = alice.getbalances()['mine']
assert_equal(balances['untrusted_pending'] + balances['trusted'], newbalance) assert_equal(balances['untrusted_pending'] + balances['trusted'], newbalance)
# Also shouldn't show up in listunspent # Also shouldn't show up in listunspent
assert not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)] assert not txABC2 in [utxo["txid"] for utxo in alice.listunspent(0)]
balance = newbalance balance = newbalance
# Abandon original transaction and verify inputs are available again # Abandon original transaction and verify inputs are available again
# including that the child tx was also abandoned # including that the child tx was also abandoned
self.nodes[0].abandontransaction(txAB1) alice.abandontransaction(txAB1)
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance + Decimal("30")) assert_equal(newbalance, balance + Decimal("30"))
balance = newbalance balance = newbalance
self.log.info("Check abandoned transactions in listsinceblock") self.log.info("Check abandoned transactions in listsinceblock")
listsinceblock = self.nodes[0].listsinceblock() listsinceblock = alice.listsinceblock()
txAB1_listsinceblock = [d for d in listsinceblock['transactions'] if d['txid'] == txAB1 and d['category'] == 'send'] txAB1_listsinceblock = [d for d in listsinceblock['transactions'] if d['txid'] == txAB1 and d['category'] == 'send']
for tx in txAB1_listsinceblock: for tx in txAB1_listsinceblock:
assert_equal(tx['abandoned'], True) assert_equal(tx['abandoned'], True)
@ -128,49 +134,53 @@ class AbandonConflictTest(BitcoinTestFramework):
# Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned # Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned
self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"]) self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"])
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
assert self.nodes[0].getmempoolinfo()['loaded'] assert self.nodes[0].getmempoolinfo()['loaded']
assert_equal(len(self.nodes[0].getrawmempool()), 0) assert_equal(len(self.nodes[0].getrawmempool()), 0)
assert_equal(self.nodes[0].getbalance(), balance) assert_equal(alice.getbalance(), balance)
# But if it is received again then it is unabandoned # But if it is received again then it is unabandoned
# And since now in mempool, the change is available # And since now in mempool, the change is available
# But its child tx remains abandoned # But its child tx remains abandoned
self.nodes[0].sendrawtransaction(signed["hex"]) self.nodes[0].sendrawtransaction(signed["hex"])
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance - Decimal("20") + Decimal("14.99998")) assert_equal(newbalance, balance - Decimal("20") + Decimal("14.99998"))
balance = newbalance balance = newbalance
# Send child tx again so it is unabandoned # Send child tx again so it is unabandoned
self.nodes[0].sendrawtransaction(signed2["hex"]) self.nodes[0].sendrawtransaction(signed2["hex"])
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance - Decimal("10") - Decimal("14.99998") + Decimal("24.9996")) assert_equal(newbalance, balance - Decimal("10") - Decimal("14.99998") + Decimal("24.9996"))
balance = newbalance balance = newbalance
# Remove using high relay fee again # Remove using high relay fee again
self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
assert self.nodes[0].getmempoolinfo()['loaded'] assert self.nodes[0].getmempoolinfo()['loaded']
assert_equal(len(self.nodes[0].getrawmempool()), 0) assert_equal(len(self.nodes[0].getrawmempool()), 0)
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance - Decimal("24.9996")) assert_equal(newbalance, balance - Decimal("24.9996"))
balance = newbalance balance = newbalance
self.log.info("Test transactions conflicted by a double spend") self.log.info("Test transactions conflicted by a double spend")
self.nodes[0].loadwallet("bob")
bob = self.nodes[0].get_wallet_rpc("bob")
# Create a double spend of AB1 by spending again from only A's 10 output # Create a double spend of AB1 by spending again from only A's 10 output
# Mine double spend from node 1 # Mine double spend from node 1
inputs = [] inputs = []
inputs.append({"txid": txA, "vout": nA}) inputs.append({"txid": txA, "vout": nA})
outputs = {} outputs = {}
outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999") outputs[self.nodes[1].getnewaddress()] = Decimal("3.9999")
tx = self.nodes[0].createrawtransaction(inputs, outputs) outputs[bob.getnewaddress()] = Decimal("5.9999")
signed = self.nodes[0].signrawtransactionwithwallet(tx) tx = alice.createrawtransaction(inputs, outputs)
self.nodes[1].sendrawtransaction(signed["hex"]) signed = alice.signrawtransactionwithwallet(tx)
self.generate(self.nodes[1], 1, sync_fun=self.no_op) double_spend_txid = self.nodes[1].sendrawtransaction(signed["hex"])
self.connect_nodes(0, 1) self.connect_nodes(0, 1)
self.sync_blocks() self.generate(self.nodes[1], 1)
tx_list = self.nodes[0].listtransactions() tx_list = alice.listtransactions()
conflicted = [tx for tx in tx_list if tx["confirmations"] < 0] conflicted = [tx for tx in tx_list if tx["confirmations"] < 0]
assert_equal(4, len(conflicted)) assert_equal(4, len(conflicted))
@ -179,7 +189,7 @@ class AbandonConflictTest(BitcoinTestFramework):
assert_equal(2, len(wallet_conflicts)) assert_equal(2, len(wallet_conflicts))
double_spends = [tx for tx in tx_list if tx["walletconflicts"] and tx["confirmations"] > 0] double_spends = [tx for tx in tx_list if tx["walletconflicts"] and tx["confirmations"] > 0]
assert_equal(1, len(double_spends)) assert_equal(2, len(double_spends)) # one for each output
double_spend = double_spends[0] double_spend = double_spends[0]
# Test the properties of the conflicted transactions, i.e. with confirmations < 0. # Test the properties of the conflicted transactions, i.e. with confirmations < 0.
@ -198,8 +208,19 @@ class AbandonConflictTest(BitcoinTestFramework):
assert_equal(double_spend["walletconflicts"], [tx["txid"]]) assert_equal(double_spend["walletconflicts"], [tx["txid"]])
assert_equal(tx["walletconflicts"], [double_spend["txid"]]) assert_equal(tx["walletconflicts"], [double_spend["txid"]])
# Test walletconflicts on the receiver's side
txinfo = bob.gettransaction(txAB1)
assert_equal(txinfo['confirmations'], -1)
assert_equal(txinfo['walletconflicts'], [double_spend['txid']])
double_spends = [tx for tx in bob.listtransactions() if tx["walletconflicts"] and tx["confirmations"] > 0]
assert_equal(1, len(double_spends))
double_spend = double_spends[0]
assert_equal(double_spend_txid, double_spend['txid'])
assert_equal(double_spend["walletconflicts"], [txAB1])
# Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted # Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
assert_equal(newbalance, balance + Decimal("20")) assert_equal(newbalance, balance + Decimal("20"))
balance = newbalance balance = newbalance
@ -207,7 +228,7 @@ class AbandonConflictTest(BitcoinTestFramework):
# Invalidate the block with the double spend and B's 10 BTC output should no longer be available # Invalidate the block with the double spend and B's 10 BTC output should no longer be available
# Don't think C's should either # Don't think C's should either
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
newbalance = self.nodes[0].getbalance() newbalance = alice.getbalance()
#assert_equal(newbalance, balance - Decimal("10")) #assert_equal(newbalance, balance - Decimal("10"))
self.log.info("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer") self.log.info("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer")
self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315") self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315")