diff options
-rw-r--r-- | doc/policy/mempool-replacements.md | 5 | ||||
-rw-r--r-- | doc/release-notes.md | 4 | ||||
-rw-r--r-- | src/init.cpp | 1 | ||||
-rw-r--r-- | src/kernel/mempool_options.h | 3 | ||||
-rw-r--r-- | src/mempool_args.cpp | 2 | ||||
-rw-r--r-- | src/rpc/mempool.cpp | 2 | ||||
-rw-r--r-- | src/txmempool.cpp | 1 | ||||
-rw-r--r-- | src/txmempool.h | 1 | ||||
-rw-r--r-- | src/validation.cpp | 5 | ||||
-rwxr-xr-x | test/functional/feature_rbf.py | 31 |
10 files changed, 54 insertions, 1 deletions
diff --git a/doc/policy/mempool-replacements.md b/doc/policy/mempool-replacements.md index 18f08daf88..fea0143757 100644 --- a/doc/policy/mempool-replacements.md +++ b/doc/policy/mempool-replacements.md @@ -15,6 +15,8 @@ other consensus and policy rules, each of the following conditions are met: *Rationale*: See [BIP125 explanation](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#motivation). + The Bitcoin Core implementation offers a node setting (`mempoolfullrbf`) to allow transaction + replacement without enforcement of the opt-in signaling rule. 2. The replacement transaction only include an unconfirmed input if that input was included in one of the directly conflicting transactions. An unconfirmed input spends an output from a @@ -74,3 +76,6 @@ This set of rules is similar but distinct from BIP125. * RBF enabled by default in the wallet GUI as of **v0.18.1** ([PR #11605](https://github.com/bitcoin/bitcoin/pull/11605)). + +* Full replace-by-fee enabled as a configurable mempool policy as of **v24.0** ([PR + #25353](https://github.com/bitcoin/bitcoin/pull/25353)). diff --git a/doc/release-notes.md b/doc/release-notes.md index 35f0713879..2c3bb27935 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -86,6 +86,10 @@ Changes to GUI or wallet related settings can be found in the GUI or Wallet sect New settings ------------ +- A new `mempoolfullrbf` option has been added, which enables the mempool to + accept transaction replacement without enforcing the opt-in replaceability + signal. (#25353) + Tools and Utilities ------------------- diff --git a/src/init.cpp b/src/init.cpp index d844e9b169..eff37e1a83 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -558,6 +558,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h index a14abb6628..07953b443b 100644 --- a/src/kernel/mempool_options.h +++ b/src/kernel/mempool_options.h @@ -15,6 +15,8 @@ class CBlockPolicyEstimator; static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300}; /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336}; +/** Default for -mempoolfullrbf, if the transaction replaceability signaling is ignored */ +static constexpr bool DEFAULT_MEMPOOL_FULL_RBF{false}; namespace kernel { /** @@ -31,6 +33,7 @@ struct MemPoolOptions { int check_ratio{0}; int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000}; std::chrono::seconds expiry{std::chrono::hours{DEFAULT_MEMPOOL_EXPIRY_HOURS}}; + bool full_rbf{DEFAULT_MEMPOOL_FULL_RBF}; MemPoolLimits limits{}; }; } // namespace kernel diff --git a/src/mempool_args.cpp b/src/mempool_args.cpp index e26cbe0275..77caa127e9 100644 --- a/src/mempool_args.cpp +++ b/src/mempool_args.cpp @@ -33,5 +33,7 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolOptions& mempool_opt if (auto hours = argsman.GetIntArg("-mempoolexpiry")) mempool_opts.expiry = std::chrono::hours{*hours}; + mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf); + ApplyArgsManOptions(argsman, mempool_opts.limits); } diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index fbb40ab861..84d43e7818 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -663,6 +663,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); ret.pushKV("incrementalrelayfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK())); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); + ret.pushKV("fullrbf", pool.m_full_rbf); return ret; } @@ -684,6 +685,7 @@ static RPCHelpMan getmempoolinfo() {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"}, {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}, + {RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"}, }}, RPCExamples{ HelpExampleCli("getmempoolinfo", "") diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 69ae9fed99..aeaa10034e 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -458,6 +458,7 @@ CTxMemPool::CTxMemPool(const Options& opts) minerPolicyEstimator{opts.estimator}, m_max_size_bytes{opts.max_size_bytes}, m_expiry{opts.expiry}, + m_full_rbf{opts.full_rbf}, m_limits{opts.limits} { _clear(); //lock free clear diff --git a/src/txmempool.h b/src/txmempool.h index f44e78fde5..6e37f59f2e 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -568,6 +568,7 @@ public: const int64_t m_max_size_bytes; const std::chrono::seconds m_expiry; + const bool m_full_rbf; using Limits = kernel::MemPoolLimits; diff --git a/src/validation.cpp b/src/validation.cpp index 6b21d33871..4c694a2c21 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -740,7 +740,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Applications relying on first-seen mempool behavior should // check all unconfirmed ancestors; otherwise an opt-in ancestor // might be replaced, causing removal of this descendant. - if (!SignalsOptInRBF(*ptxConflicting)) { + // + // If replaceability signaling is ignored due to node setting, + // replacement is always allowed. + if (!m_pool.m_full_rbf && !SignalsOptInRBF(*ptxConflicting)) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); } diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 40ad2137d4..8e5cbba01a 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -83,6 +83,9 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.log.info("Running test replacement relay fee...") self.test_replacement_relay_fee() + self.log.info("Running test full replace by fee...") + self.test_fullrbf() + self.log.info("Passed") def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None): @@ -698,5 +701,33 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx.vout[0].nValue -= 1 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) + def test_fullrbf(self): + txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid'] + self.generate(self.nodes[0], 1) + confirmed_utxo = self.wallet.get_utxo(txid=txid) + + self.restart_node(0, extra_args=["-mempoolfullrbf=1"]) + + # Create an explicitly opt-out transaction + optout_tx = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=confirmed_utxo, + sequence=SEQUENCE_FINAL, + fee_rate=Decimal('0.01'), + ) + assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable']) + + conflicting_tx = self.wallet.create_self_transfer( + utxo_to_spend=confirmed_utxo, + sequence=SEQUENCE_FINAL, + fee_rate=Decimal('0.02'), + ) + + # Send the replacement transaction, conflicting with the optout_tx. + self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0) + + # Optout_tx is not anymore in the mempool. + assert optout_tx['txid'] not in self.nodes[0].getrawmempool() + if __name__ == '__main__': ReplaceByFeeTest().main() |