From 9169a50d529efeae465e55947978f5e470d7f7d0 Mon Sep 17 00:00:00 2001 From: glozow Date: Thu, 14 Aug 2025 13:07:55 -0400 Subject: [PATCH 1/4] [rpc] expose blockmintxfee via getmininginfo --- src/rpc/mining.cpp | 4 ++++ test/functional/mining_basic.py | 1 + 2 files changed, 5 insertions(+) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index c66b6c1984a..e0e355693ef 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -429,6 +429,7 @@ static RPCHelpMan getmininginfo() {RPCResult::Type::STR_HEX, "target", "The current target"}, {RPCResult::Type::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, + {RPCResult::Type::STR_AMOUNT, "blockmintxfee", "Minimum feerate of packages selected for block inclusion in " + CURRENCY_UNIT + "/kvB"}, {RPCResult::Type::STR, "chain", "current network name (" LIST_CHAIN_NAMES ")"}, {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "The block challenge (aka. block script), in hexadecimal (only present if the current network is a signet)"}, {RPCResult::Type::OBJ, "next", "The next block", @@ -469,6 +470,9 @@ static RPCHelpMan getmininginfo() obj.pushKV("target", GetTarget(tip, chainman.GetConsensus().powLimit).GetHex()); obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); + BlockAssembler::Options assembler_options; + ApplyArgsManOptions(*node.args, assembler_options); + obj.pushKV("blockmintxfee", ValueFromAmount(assembler_options.blockMinFeeRate.GetFeePerK())); obj.pushKV("chain", chainman.GetParams().GetChainTypeString()); UniValue next(UniValue::VOBJ); diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 4683919d1cc..4f104359d04 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -153,6 +153,7 @@ class MiningTest(BitcoinTestFramework): self.log.info(f"-> Test {blockmintxfee_parameter} ({blockmintxfee_sat_kvb} sat/kvB)...") self.restart_node(0, extra_args=[blockmintxfee_parameter, '-minrelaytxfee=0', '-persistmempool=0']) self.wallet.rescan_utxos() # to avoid spending outputs of txs that are not in mempool anymore after restart + assert_equal(node.getmininginfo()['blockmintxfee'], blockmintxfee_btc_kvb) # submit one tx with exactly the blockmintxfee rate, and one slightly below tx_with_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb, confirmed_only=True) From 636fa219d37f86067d996c86fada286cedc0d78e Mon Sep 17 00:00:00 2001 From: glozow Date: Thu, 14 Aug 2025 13:24:54 -0400 Subject: [PATCH 2/4] test fixups --- test/functional/feature_rbf.py | 2 +- test/functional/mempool_package_rbf.py | 2 +- test/functional/mempool_truc.py | 4 ++++ test/functional/mining_basic.py | 7 ++++--- test/functional/wallet_bumpfee.py | 8 ++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 4117326b883..86fe2f5b65b 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -594,7 +594,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): node = self.nodes[0] for incremental_setting in (0, 5, 10, 50, 100, 234, 1000, 5000, 21000): incremental_setting_decimal = incremental_setting / Decimal(COIN) - self.log.info(f"-> Test -incrementalrelayfee={incremental_setting_decimal:.8f}sat/kvB...") + self.log.info(f"-> Test -incrementalrelayfee={incremental_setting:.8f}sat/kvB...") self.restart_node(0, extra_args=[f"-incrementalrelayfee={incremental_setting_decimal:.8f}", "-persistmempool=0"]) # When incremental relay feerate is higher than min relay feerate, min relay feerate is automatically increased. diff --git a/test/functional/mempool_package_rbf.py b/test/functional/mempool_package_rbf.py index 54f3a90c0ac..3eb0abd37e3 100755 --- a/test/functional/mempool_package_rbf.py +++ b/test/functional/mempool_package_rbf.py @@ -168,7 +168,7 @@ class PackageRBFTest(BitcoinTestFramework): failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short) assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3])) pkg_results3 = node.submitpackage(failure_package_hex3) - assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].txid_hex}, not enough additional fees to relay; {incremental_sats_short:8f} < {incremental_sats_required:8f}", pkg_results3["package_msg"]) + assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].txid_hex}, not enough additional fees to relay; {incremental_sats_short:.8f} < {incremental_sats_required:.8f}", pkg_results3["package_msg"]) self.assert_mempool_contents(expected=package_txns1) success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required) diff --git a/test/functional/mempool_truc.py b/test/functional/mempool_truc.py index 428bfb0b807..562afcd34dd 100755 --- a/test/functional/mempool_truc.py +++ b/test/functional/mempool_truc.py @@ -617,6 +617,10 @@ class MempoolTRUC(BitcoinTestFramework): assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0) # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low assert_greater_than(total_v3_fee, 0) + # Also create a version where the child is at minrelaytxfee + tx_v3_child_minrelay = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=minrelayfeerate, version=3) + result_truc_minrelay = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child_minrelay["hex"]]) + assert_equal(result_truc_minrelay["package_msg"], "transaction failed") tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2) tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 4f104359d04..7e71761ae65 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -152,19 +152,20 @@ class MiningTest(BitcoinTestFramework): blockmintxfee_parameter = f"-blockmintxfee={blockmintxfee_btc_kvb:.8f}" self.log.info(f"-> Test {blockmintxfee_parameter} ({blockmintxfee_sat_kvb} sat/kvB)...") self.restart_node(0, extra_args=[blockmintxfee_parameter, '-minrelaytxfee=0', '-persistmempool=0']) - self.wallet.rescan_utxos() # to avoid spending outputs of txs that are not in mempool anymore after restart assert_equal(node.getmininginfo()['blockmintxfee'], blockmintxfee_btc_kvb) # submit one tx with exactly the blockmintxfee rate, and one slightly below tx_with_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb, confirmed_only=True) assert_equal(tx_with_min_feerate["fee"], get_fee(tx_with_min_feerate["tx"].get_vsize(), blockmintxfee_btc_kvb)) - if blockmintxfee_sat_kvb > 5: + if blockmintxfee_sat_kvb >= 10: lowerfee_btc_kvb = blockmintxfee_btc_kvb - Decimal(10)/COIN # 0.01 sat/vbyte lower + assert_greater_than(blockmintxfee_btc_kvb, lowerfee_btc_kvb) + assert_greater_than_or_equal(lowerfee_btc_kvb, 0) tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=lowerfee_btc_kvb, confirmed_only=True) assert_equal(tx_below_min_feerate["fee"], get_fee(tx_below_min_feerate["tx"].get_vsize(), lowerfee_btc_kvb)) else: # go below zero fee by using modified fees tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb, confirmed_only=True) - node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -1) + node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -11) # check that tx below specified fee-rate is neither in template nor in the actual block block_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 097c39575a3..d3972bef52e 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -848,12 +848,12 @@ def test_bumpfee_with_feerate_ignores_walletincrementalrelayfee(self, rbf_node, assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 1}) assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2}) - # Ensure you can not fee bump if the fee_rate is more than original fee_rate but the total fee from new fee_rate is - # less than (original fee + incrementalrelayfee) - assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2.05}) + # Ensure you can not fee bump if the fee_rate is more than original fee_rate but the additional fee does + # not cover incrementalrelayfee for the size of the replacement transaction + assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2.09}) # You can fee bump as long as the new fee set from fee_rate is at least (original fee + incrementalrelayfee) - rbf_node.bumpfee(tx["txid"], {"fee_rate": 3}) + rbf_node.bumpfee(tx["txid"], {"fee_rate": 2.1}) self.clear_mempool() From c568511e8ced011103ef6e3616409fa0ac54408c Mon Sep 17 00:00:00 2001 From: glozow Date: Thu, 14 Aug 2025 15:34:35 -0400 Subject: [PATCH 3/4] test fixup for incremental feerate Clarify that the purpose of some parameters are to ensure identical transactions are not created. Also, strengthen the test to catch these cases. --- test/functional/feature_rbf.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 86fe2f5b65b..6922b5e0f40 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -13,7 +13,6 @@ from test_framework.messages import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - assert_greater_than, assert_greater_than_or_equal, assert_raises_rpc_error, get_fee, @@ -603,23 +602,27 @@ class ReplaceByFeeTest(BitcoinTestFramework): low_feerate = min_relay_feerate * 2 confirmed_utxo = self.wallet.get_utxo(confirmed_only=True) - replacee_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee_rate=low_feerate, target_vsize=5000) + # Use different versions to avoid creating an identical transaction when failed_replacement_tx is created. + # Use a target vsize that is small, but something larger than the minimum so that we can create a transaction that is 1vB smaller later. + replacee_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee_rate=low_feerate, version=3, target_vsize=200) node.sendrawtransaction(replacee_tx['hex']) - replacement_placeholder_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo) + replacement_placeholder_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, target_vsize=200) replacement_expected_size = replacement_placeholder_tx['tx'].get_vsize() replacement_required_fee = get_fee(replacement_expected_size, incremental_setting_decimal) + replacee_tx['fee'] - # Should always be required to pay additional fees - if incremental_setting > 0: - assert_greater_than(replacement_required_fee, replacee_tx['fee']) - - # 1 satoshi shy of the required fee - failed_replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee - Decimal("0.00000001")) + # Show that replacement fails when paying 1 satoshi shy of the required fee + failed_replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee - Decimal("0.00000001"), version=2, target_vsize=200) assert_raises_rpc_error(-26, "insufficient fee", node.sendrawtransaction, failed_replacement_tx['hex']) + replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee, version=2, target_vsize=200) - replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee) - node.sendrawtransaction(replacement_tx['hex']) + if incremental_setting == 0: + # When incremental relay feerate is 0, additional fees are not required, but higher feerate is still required. + assert_raises_rpc_error(-26, "insufficient fee", node.sendrawtransaction, replacement_tx['hex']) + replacement_tx_smaller = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee, version=2, target_vsize=199) + node.sendrawtransaction(replacement_tx_smaller['hex']) + else: + node.sendrawtransaction(replacement_tx['hex']) def test_fullrbf(self): # BIP125 signaling is not respected From daa40a3ff97346face9dcc64564010a66c91ccb2 Mon Sep 17 00:00:00 2001 From: glozow Date: Fri, 15 Aug 2025 11:23:46 -0400 Subject: [PATCH 4/4] doc fixups for 33106 --- doc/release-notes-33106.md | 3 +-- src/test/miner_tests.cpp | 3 ++- test/functional/feature_rbf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/release-notes-33106.md b/doc/release-notes-33106.md index 95750cedff9..f57ae6e4762 100644 --- a/doc/release-notes-33106.md +++ b/doc/release-notes-33106.md @@ -9,8 +9,7 @@ changed to 100 satoshis per kvB. They can still be changed using their respectiv recommended to change both together if you decide to do so. Other minimum feerates (e.g. the dust feerate, the minimum returned by the fee estimator, and all feerates used by the -wallet) remain unchanged. The mempool minimum feerate still changes in response to high volume but more gradually, as a -result of the change to the incremental relay feerate. +wallet) remain unchanged. The mempool minimum feerate still changes in response to high volume. Note that unless these lower defaults are widely adopted across the network, transactions created with lower fee rates are not guaranteed to propagate or confirm. The wallet feerates remain unchanged; `-mintxfee` must be changed before diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 156f6e5b4a0..652ec25fe89 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -218,7 +218,8 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const tx.vout[0].nValue = 5000000000LL - 100000000; tx.vout[1].nValue = 100000000; // 1BTC output // Increase size to avoid rounding errors: when the feerate is extremely small (i.e. 1sat/kvB), evaluating the fee - // at a smaller transaction size gives us a rounded value of 0. + // at smaller sizes gives us rounded values that are equal to each other, which means we incorrectly include + // hashFreeTx2 + hashLowFeeTx2. BulkTransaction(tx, 4000); Txid hashFreeTx2 = tx.GetHash(); AddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 6922b5e0f40..443910c0ad5 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -583,7 +583,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['tx'] # Higher fee, higher feerate, different txid, but the replacement does not provide a relay - # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB. + # fee conforming to node's `incrementalrelayfee` policy of 100 sat per KB. assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.000001")) tx.vout[0].nValue -= 1 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())