From b0131c96f0640c200e7abe22f8c7f19512b1cd55 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 26 May 2025 14:13:50 +0200 Subject: [PATCH] wallet.bump_fee: do not change nsequence of already existing inputs Fixes tx rejection by network if the original tx has a csv: > non-mandatory-script-verify-flag (Locktime requirement not satisfied) Also add unit test --- electrum/wallet.py | 2 +- tests/test_wallet_vertical.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/electrum/wallet.py b/electrum/wallet.py index c1f8096b3..44c36dcfd 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -2250,7 +2250,6 @@ class Abstract_Wallet(ABC, Logger, EventListener): f"got {actual_fee}, expected >={target_min_fee}. " f"target rate was {new_fee_rate}") tx_new.locktime = get_locktime_for_new_transaction(self.network) - tx_new.set_rbf(True) tx_new.add_info_from_wallet(self) return tx_new @@ -2298,6 +2297,7 @@ class Abstract_Wallet(ABC, Logger, EventListener): coins = [c for c in coins if c.prevout.txid.hex() not in self.adb.get_conflicting_transactions(tx, include_self=True)] for item in coins: + item.nsequence = 0xffffffff - 2 self.add_input_info(item) def fee_estimator(size): return FeePolicy.estimate_fee_for_feerate(fee_per_kb=new_fee_rate*1000, size=size) diff --git a/tests/test_wallet_vertical.py b/tests/test_wallet_vertical.py index ec785175e..d47fcdf9f 100644 --- a/tests/test_wallet_vertical.py +++ b/tests/test_wallet_vertical.py @@ -1265,6 +1265,9 @@ class TestWalletSending(ElectrumTestCase): with TmpConfig() as config: with self.subTest(msg="_bump_fee_p2wpkh_insane_high_target_fee", simulate_moving_txs=simulate_moving_txs): await self._bump_fee_p2wpkh_insane_high_target_fee(config=config) + with TmpConfig() as config: + with self.subTest(msg="_bump_fee_p2wpkh_csv", simulate_moving_txs=simulate_moving_txs): + await self._bump_fee_p2wpkh_csv(config=config) async def _bump_fee_p2pkh_when_there_is_a_change_address(self, *, simulate_moving_txs, config): wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean', @@ -1493,6 +1496,33 @@ class TestWalletSending(ElectrumTestCase): tx.version = 2 self.assertEqual('6b03c00f47cb145ffb632c3ce54dece29b9a980949ef5c574321f7fc83fa2238', tx.txid()) + async def _bump_fee_p2wpkh_csv(self, *, config): + wallet = self.create_standard_wallet_from_seed('leader company camera enlist crash sleep insane aware anger hole hammer label', + config=config) + + # bootstrap wallet + funding_tx = Transaction('020000000001022ea8f7940c2e4bca2f34f21ba15a5c8d5e3c93d9c6deb17983412feefa0f1f6d0100000000fdffffff9d4ba5ab41951d506a7fa8272ef999ce3df166fe28f6f885aa791f012a0924cf0000000000fdffffff027485010000000000160014f80e86af4246960a24cd21c275a8e8842973fbcaa0860100000000001600149c6b743752604b98d30f1a5d27a5d5ce8919f4400247304402203bf6dd875a775f356d4bb8c4e295a2cd506338c100767518f2b31fb85db71c1302204dc4ebca5584fc1cc08bd7f7171135d1b67ca6c8812c3723cd332eccaa7b848101210360bdbd16d9ef390fd3e804c421e6f30e6b065ac314f4d2b9a80d2f0682ad1431024730440220126b442d7988c5883ca17c2429f51ce770e3a57895524c8dfe07b539e483019e02200b50feed4f42f0035c9a9ddd044820607281e45e29e41a29233c2b8be6080bac01210245d47d08915816a5ecc934cff1b17e00071ca06172f51d632ba95392e8aad4fdd38a1d00') + funding_txid = funding_tx.txid() + self.assertEqual('dd0bf0d1563cd588b4c93cc1a9623c051ddb1c4f4581cf8ef43cfd27f031f246', funding_txid) + wallet.adb.receive_tx_callback(funding_tx, tx_height=TX_HEIGHT_UNCONFIRMED) + + orig_rbf_tx = Transaction("0200000000010146f231f027fd3cf48ecf81454f1cdb1d053c62a9c13cc9b488d53c56d1f00bdd01000000000100000002c8af000000000000160014999a95482213a896c72a251b6cc9f3d137b0a45850c3000000000000160014ea76d391236726af7d7a9c10abe600129154eb5a02473044022076d298537b524a926a8fadad0e9ded5868c8f4cf29246048f76f00eb4afa56310220739ad9e0417e97ce03fad98a454b4977972c2805cef37bfa822c6d6c56737c870121024196fb7b766ac987a08b69a5e108feae8513b7e72bc9e47899e27b36100f2af4d48a1d00") + orig_rbf_txid = orig_rbf_tx.txid() + self.assertEqual('726f97590389fbef8570609eb2f8640464edb8ef44c30e48f4082b0191fa699c', orig_rbf_txid) + self.assertEqual(orig_rbf_tx.get_block_based_relative_locktime(), 1) + + wallet.adb.receive_tx_callback(orig_rbf_tx, tx_height=TX_HEIGHT_UNCONFIRMED) + + tx = wallet.bump_fee( + tx=tx_from_any(orig_rbf_tx.serialize()), + new_fee_rate=60, + strategy=BumpFeeStrategy.DECREASE_PAYMENT, + ) + tx.locktime = 1936085 + tx.version = 2 + self.assertEqual(tx.get_block_based_relative_locktime(), 1) + self.assertEqual('9f1842ea9c4d7cf88ac58d55d1b73e6ad7d34693a046d428887ead2c22865483', tx.txid()) + @mock.patch.object(wallet.Abstract_Wallet, 'save_db') async def test_cpfp_p2pkh(self, mock_save_db): wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean')