aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine Riard <dev@ariard.me>2022-04-27 10:25:22 -0400
committerAntoine Riard <dev@ariard.me>2022-07-06 20:57:29 -0400
commit3e27e317270fdc2dd02794fea9da016387699636 (patch)
tree4ca7453d07febd54f8dff13a8dbf61fbe7b685f0
parent5bc10b39abbcb77638161902ccd1225139bc7cc0 (diff)
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.
-rw-r--r--src/init.cpp1
-rw-r--r--src/kernel/mempool_options.h3
-rw-r--r--src/mempool_args.cpp2
-rw-r--r--src/txmempool.cpp1
-rw-r--r--src/txmempool.h1
-rw-r--r--src/validation.cpp5
-rwxr-xr-xtest/functional/feature_rbf.py31
7 files changed, 43 insertions, 1 deletions
diff --git a/src/init.cpp b/src/init.cpp
index ee90abcc93..e17728373b 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/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 91dc222bab..5558c76138 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -94,6 +94,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=DUMMY_P2WPKH_SCRIPT):
@@ -714,5 +717,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()