From f9cac635237142090271022164fa5d58e014493d Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Fri, 21 Jul 2023 10:42:41 -0400 Subject: [PATCH] test: cover testmempoolaccept debug-message in RBF test --- test/functional/feature_rbf.py | 60 +++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index d9700e2ee2..ef2ecfab9e 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -103,14 +103,22 @@ class ReplaceByFeeTest(BitcoinTestFramework): """Simple doublespend""" # we use MiniWallet to create a transaction template with inputs correctly set, # and modify the output (amount, scriptPubKey) according to our needs - tx = self.wallet.create_self_transfer()["tx"] + tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.003"))["tx"] tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex()) # Should fail because we haven't changed the fee tx.vout[0].scriptPubKey[-1] ^= 1 + tx.rehash() + tx_hex = tx.serialize().hex() # This will raise an exception due to insufficient fee - assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex(), 0) + reject_reason = "insufficient fee" + reject_details = f"{reject_reason}, rejecting replacement {tx.hash}; new feerate 0.00300000 BTC/kvB <= old feerate 0.00300000 BTC/kvB" + res = self.nodes[0].testmempoolaccept(rawtxs=[tx_hex])[0] + assert_equal(res["reject-reason"], reject_reason) + assert_equal(res["reject-details"], reject_details) + assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx_hex, 0) + # Extra 0.1 BTC fee tx.vout[0].nValue -= int(0.1 * COIN) @@ -154,7 +162,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx_hex = dbl_tx.serialize().hex() # This will raise an exception due to insufficient fee - assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) + reject_reason = "insufficient fee" + reject_details = f"{reject_reason}, rejecting replacement {dbl_tx.hash}, less fees than conflicting txs; 3.00 < 4.00" + res = self.nodes[0].testmempoolaccept(rawtxs=[dbl_tx_hex])[0] + assert_equal(res["reject-reason"], reject_reason) + assert_equal(res["reject-details"], reject_details) + assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) + + # Accepted with sufficient fee dbl_tx.vout[0].nValue = int(0.1 * COIN) @@ -273,22 +288,30 @@ class ReplaceByFeeTest(BitcoinTestFramework): utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN)) utxo2 = self.make_utxo(self.nodes[0], 3 * COIN) - tx1a_utxo = self.wallet.send_self_transfer( + tx1a = self.wallet.send_self_transfer( from_node=self.nodes[0], utxo_to_spend=utxo1, sequence=0, fee=Decimal("0.1"), - )["new_utxo"] + ) + tx1a_utxo = tx1a["new_utxo"] # Direct spend an output of the transaction we're replacing. - tx2_hex = self.wallet.create_self_transfer_multi( + tx2 = self.wallet.create_self_transfer_multi( utxos_to_spend=[utxo1, utxo2, tx1a_utxo], sequence=0, amount_per_output=int(COIN * tx1a_utxo["value"]), - )["hex"] + )["tx"] + tx2_hex = tx2.serialize().hex() # This will raise an exception - assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) + reject_reason = "bad-txns-spends-conflicting-tx" + reject_details = f"{reject_reason}, {tx2.hash} spends conflicting transaction {tx1a['tx'].hash}" + res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0] + assert_equal(res["reject-reason"], reject_reason) + assert_equal(res["reject-details"], reject_details) + assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0) + # Spend tx1a's output to test the indirect case. tx1b_utxo = self.wallet.send_self_transfer( @@ -319,14 +342,21 @@ class ReplaceByFeeTest(BitcoinTestFramework): fee=Decimal("0.1"), ) - tx2_hex = self.wallet.create_self_transfer_multi( + tx2 = self.wallet.create_self_transfer_multi( utxos_to_spend=[confirmed_utxo, unconfirmed_utxo], sequence=0, amount_per_output=1 * COIN, - )["hex"] + )["tx"] + tx2_hex = tx2.serialize().hex() # This will raise an exception - assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0) + reject_reason = "replacement-adds-unconfirmed" + reject_details = f"{reject_reason}, replacement {tx2.hash} adds unconfirmed input, idx 1" + res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0] + assert_equal(res["reject-reason"], reject_reason) + assert_equal(res["reject-details"], reject_details) + assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0) + def test_too_many_replacements(self): """Replacements that evict too many transactions are rejected""" @@ -368,7 +398,13 @@ class ReplaceByFeeTest(BitcoinTestFramework): double_tx_hex = double_tx.serialize().hex() # This will raise an exception - assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0) + reject_reason = "too many potential replacements" + reject_details = f"{reject_reason}, rejecting replacement {double_tx.hash}; too many potential replacements ({MAX_REPLACEMENT_LIMIT + 1} > {MAX_REPLACEMENT_LIMIT})" + res = self.nodes[0].testmempoolaccept(rawtxs=[double_tx_hex])[0] + assert_equal(res["reject-reason"], reject_reason) + assert_equal(res["reject-details"], reject_details) + assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, double_tx_hex, 0) + # If we remove an input, it should pass double_tx.vin.pop()