Skip to content

Commit a7f3479

Browse files
author
MacroFake
committed
Merge bitcoin#25353: Add a -mempoolfullrbf node setting
4c9666b Mention `mempoolfullrbf` in policy/mempool-replacements.md (Antoine Riard) aae66ab Update getmempoolinfo RPC with `mempoolfullrbf` (Antoine Riard) 3e27e31 Introduce `mempoolfullrbf` node setting. (Antoine Riard) Pull request description: This is ready for review. Recent discussions among LN devs have brought back on the surface concerns about the security of multi-party funded transactions against pinnings attacks and other mempool-based nuisances. The lack of full-rbf transaction-relay topology connected to miners open the way to cheap and naive DoS against multi-party funded transactions (e.g coinjoins, dual-funded channels, on-chain DLCs, ...) without solutions introducing an overhead cost or centralization vectors afaik . For more details, see [0]. This PR implements a simple `fullrbf` setting, where the node always allows transaction replacement, ignoring BIP125 opt-in flag. The default value of the setting stays **false**, therefore opt-in replacement is still the default Bitcoin Core replacement policy. Contrary to a previous proposal of mine and listening to feedbacks collected since then [1], I think this new setting simply offers more flexibility in a node transaction-relay policy suiting one's application requirements, without arguing a change of the default behavior. I [posted](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-June/020557.html) on the ML to invite operators with a bitcoin application sensitive to full-rbf (e.g dual-funded LN channels service providers) or mempool researchers to join a bootstrapped full-rbf activated peers network for experimentation and learning. If people have strong opinions against the existence of such full-rbf transaction-relay network, I'm proposing to express them on the future thread. [0] https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-May/003033.html [1] https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-June/019074.html Follow-up suggestions : - soft-enable opt-in RBF in the wallet : bitcoin#25353 (comment) - p2p discovery and additional outbound connection to full-rbf peers : bitcoin#25353 (comment) - match the code between RPC, wallet and mempool about disregard of inherited signaling : bitcoin#22698 ACKs for top commit: instagibbs: reACK bitcoin@4c9666b glozow: ACK 4c9666b, a few nits which are non-blocking. w0xlt: ACK bitcoin@4c9666b Tree-SHA512: 9e288bf22e06a9808804e58178444ef1830c3fdd42fd8a7cd7ffb101f8f586e08b000679be407d63ca76a56f7216227b368ff630c81f3fac3243db1a1202ab1c
2 parents 172823e + 4c9666b commit a7f3479

10 files changed

+54
-1
lines changed

doc/policy/mempool-replacements.md

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ other consensus and policy rules, each of the following conditions are met:
1515

1616
*Rationale*: See [BIP125
1717
explanation](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#motivation).
18+
The Bitcoin Core implementation offers a node setting (`mempoolfullrbf`) to allow transaction
19+
replacement without enforcement of the opt-in signaling rule.
1820

1921
2. The replacement transaction only include an unconfirmed input if that input was included in
2022
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.
7476

7577
* RBF enabled by default in the wallet GUI as of **v0.18.1** ([PR
7678
#11605](https://github.com/bitcoin/bitcoin/pull/11605)).
79+
80+
* Full replace-by-fee enabled as a configurable mempool policy as of **v24.0** ([PR
81+
#25353](https://github.com/bitcoin/bitcoin/pull/25353)).

doc/release-notes.md

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ Changes to GUI or wallet related settings can be found in the GUI or Wallet sect
8686
New settings
8787
------------
8888

89+
- A new `mempoolfullrbf` option has been added, which enables the mempool to
90+
accept transaction replacement without enforcing the opt-in replaceability
91+
signal. (#25353)
92+
8993
Tools and Utilities
9094
-------------------
9195

src/init.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ void SetupServerArgs(ArgsManager& argsman)
558558
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);
559559
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
560560
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);
561+
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);
561562
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
562563
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
563564
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);

src/kernel/mempool_options.h

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class CBlockPolicyEstimator;
1515
static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300};
1616
/** Default for -mempoolexpiry, expiration time for mempool transactions in hours */
1717
static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336};
18+
/** Default for -mempoolfullrbf, if the transaction replaceability signaling is ignored */
19+
static constexpr bool DEFAULT_MEMPOOL_FULL_RBF{false};
1820

1921
namespace kernel {
2022
/**
@@ -31,6 +33,7 @@ struct MemPoolOptions {
3133
int check_ratio{0};
3234
int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000};
3335
std::chrono::seconds expiry{std::chrono::hours{DEFAULT_MEMPOOL_EXPIRY_HOURS}};
36+
bool full_rbf{DEFAULT_MEMPOOL_FULL_RBF};
3437
MemPoolLimits limits{};
3538
};
3639
} // namespace kernel

src/mempool_args.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolOptions& mempool_opt
3333

3434
if (auto hours = argsman.GetIntArg("-mempoolexpiry")) mempool_opts.expiry = std::chrono::hours{*hours};
3535

36+
mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
37+
3638
ApplyArgsManOptions(argsman, mempool_opts.limits);
3739
}

src/rpc/mempool.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
663663
ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
664664
ret.pushKV("incrementalrelayfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
665665
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
666+
ret.pushKV("fullrbf", pool.m_full_rbf);
666667
return ret;
667668
}
668669

@@ -684,6 +685,7 @@ static RPCHelpMan getmempoolinfo()
684685
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
685686
{RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"},
686687
{RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"},
688+
{RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"},
687689
}},
688690
RPCExamples{
689691
HelpExampleCli("getmempoolinfo", "")

src/txmempool.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ CTxMemPool::CTxMemPool(const Options& opts)
458458
minerPolicyEstimator{opts.estimator},
459459
m_max_size_bytes{opts.max_size_bytes},
460460
m_expiry{opts.expiry},
461+
m_full_rbf{opts.full_rbf},
461462
m_limits{opts.limits}
462463
{
463464
_clear(); //lock free clear

src/txmempool.h

+1
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ class CTxMemPool
568568

569569
const int64_t m_max_size_bytes;
570570
const std::chrono::seconds m_expiry;
571+
const bool m_full_rbf;
571572

572573
using Limits = kernel::MemPoolLimits;
573574

src/validation.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
740740
// Applications relying on first-seen mempool behavior should
741741
// check all unconfirmed ancestors; otherwise an opt-in ancestor
742742
// might be replaced, causing removal of this descendant.
743-
if (!SignalsOptInRBF(*ptxConflicting)) {
743+
//
744+
// If replaceability signaling is ignored due to node setting,
745+
// replacement is always allowed.
746+
if (!m_pool.m_full_rbf && !SignalsOptInRBF(*ptxConflicting)) {
744747
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
745748
}
746749

test/functional/feature_rbf.py

+31
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def run_test(self):
8383
self.log.info("Running test replacement relay fee...")
8484
self.test_replacement_relay_fee()
8585

86+
self.log.info("Running test full replace by fee...")
87+
self.test_fullrbf()
88+
8689
self.log.info("Passed")
8790

8891
def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
@@ -698,5 +701,33 @@ def test_replacement_relay_fee(self):
698701
tx.vout[0].nValue -= 1
699702
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
700703

704+
def test_fullrbf(self):
705+
txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
706+
self.generate(self.nodes[0], 1)
707+
confirmed_utxo = self.wallet.get_utxo(txid=txid)
708+
709+
self.restart_node(0, extra_args=["-mempoolfullrbf=1"])
710+
711+
# Create an explicitly opt-out transaction
712+
optout_tx = self.wallet.send_self_transfer(
713+
from_node=self.nodes[0],
714+
utxo_to_spend=confirmed_utxo,
715+
sequence=SEQUENCE_FINAL,
716+
fee_rate=Decimal('0.01'),
717+
)
718+
assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable'])
719+
720+
conflicting_tx = self.wallet.create_self_transfer(
721+
utxo_to_spend=confirmed_utxo,
722+
sequence=SEQUENCE_FINAL,
723+
fee_rate=Decimal('0.02'),
724+
)
725+
726+
# Send the replacement transaction, conflicting with the optout_tx.
727+
self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0)
728+
729+
# Optout_tx is not anymore in the mempool.
730+
assert optout_tx['txid'] not in self.nodes[0].getrawmempool()
731+
701732
if __name__ == '__main__':
702733
ReplaceByFeeTest().main()

0 commit comments

Comments
 (0)