Skip to content

Commit 3e27e31

Browse files
committed
Introduce mempoolfullrbf node setting.
This new node policy setting enables to accept replaced-by-fee transaction without inspection of the replaceability signaling as described in BIP125 "explicit signaling". If turns on, the node mempool accepts transaction replacement as described in `policy/mempool-replacements.md`. The default setting value is `false`, implying opt-in RBF is enforced.
1 parent 5bc10b3 commit 3e27e31

File tree

7 files changed

+43
-1
lines changed

7 files changed

+43
-1
lines changed

src/init.cpp

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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/txmempool.cpp

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def run_test(self):
9494
self.log.info("Running test replacement relay fee...")
9595
self.test_replacement_relay_fee()
9696

97+
self.log.info("Running test full replace by fee...")
98+
self.test_fullrbf()
99+
97100
self.log.info("Passed")
98101

99102
def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
@@ -714,5 +717,33 @@ def test_replacement_relay_fee(self):
714717
tx.vout[0].nValue -= 1
715718
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
716719

720+
def test_fullrbf(self):
721+
txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
722+
self.generate(self.nodes[0], 1)
723+
confirmed_utxo = self.wallet.get_utxo(txid=txid)
724+
725+
self.restart_node(0, extra_args=["-mempoolfullrbf=1"])
726+
727+
# Create an explicitly opt-out transaction
728+
optout_tx = self.wallet.send_self_transfer(
729+
from_node=self.nodes[0],
730+
utxo_to_spend=confirmed_utxo,
731+
sequence=SEQUENCE_FINAL,
732+
fee_rate=Decimal('0.01'),
733+
)
734+
assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable'])
735+
736+
conflicting_tx = self.wallet.create_self_transfer(
737+
utxo_to_spend=confirmed_utxo,
738+
sequence=SEQUENCE_FINAL,
739+
fee_rate=Decimal('0.02'),
740+
)
741+
742+
# Send the replacement transaction, conflicting with the optout_tx.
743+
self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0)
744+
745+
# Optout_tx is not anymore in the mempool.
746+
assert optout_tx['txid'] not in self.nodes[0].getrawmempool()
747+
717748
if __name__ == '__main__':
718749
ReplaceByFeeTest().main()

0 commit comments

Comments
 (0)