Merge bitcoin/bitcoin#25353: Add a -mempoolfullrbf node setting

4c9666bd73 Mention `mempoolfullrbf` in policy/mempool-replacements.md (Antoine Riard)
aae66ab43d Update getmempoolinfo RPC with `mempoolfullrbf` (Antoine Riard)
3e27e31727 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 : https://github.com/bitcoin/bitcoin/pull/25353#issuecomment-1154918789
  - p2p discovery and additional outbound connection to full-rbf peers : https://github.com/bitcoin/bitcoin/pull/25353#issuecomment-1156044401
  - match the code between RPC, wallet and mempool about disregard of inherited signaling : #22698

ACKs for top commit:
  instagibbs:
    reACK 4c9666bd73
  glozow:
    ACK 4c9666bd73, a few nits which are non-blocking.
  w0xlt:
    ACK 4c9666bd73

Tree-SHA512: 9e288bf22e06a9808804e58178444ef1830c3fdd42fd8a7cd7ffb101f8f586e08b000679be407d63ca76a56f7216227b368ff630c81f3fac3243db1a1202ab1c
This commit is contained in:
MacroFake 2022-07-08 11:04:55 +02:00
commit a7f3479ba3
No known key found for this signature in database
GPG Key ID: CE2B75697E69A548
10 changed files with 54 additions and 1 deletions

View File

@ -15,6 +15,8 @@ other consensus and policy rules, each of the following conditions are met:
*Rationale*: See [BIP125 *Rationale*: See [BIP125
explanation](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#motivation). 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 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 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 * RBF enabled by default in the wallet GUI as of **v0.18.1** ([PR
#11605](https://github.com/bitcoin/bitcoin/pull/11605)). #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)).

View File

@ -86,6 +86,10 @@ Changes to GUI or wallet related settings can be found in the GUI or Wallet sect
New settings 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 Tools and Utilities
------------------- -------------------

View File

@ -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("-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("-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("-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)", 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); 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); 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);

View File

@ -15,6 +15,8 @@ class CBlockPolicyEstimator;
static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300}; static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300};
/** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */
static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336}; 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 { namespace kernel {
/** /**
@ -31,6 +33,7 @@ struct MemPoolOptions {
int check_ratio{0}; int check_ratio{0};
int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000}; int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000};
std::chrono::seconds expiry{std::chrono::hours{DEFAULT_MEMPOOL_EXPIRY_HOURS}}; std::chrono::seconds expiry{std::chrono::hours{DEFAULT_MEMPOOL_EXPIRY_HOURS}};
bool full_rbf{DEFAULT_MEMPOOL_FULL_RBF};
MemPoolLimits limits{}; MemPoolLimits limits{};
}; };
} // namespace kernel } // namespace kernel

View File

@ -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}; 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); ApplyArgsManOptions(argsman, mempool_opts.limits);
} }

View File

@ -663,6 +663,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
ret.pushKV("incrementalrelayfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK())); ret.pushKV("incrementalrelayfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
ret.pushKV("fullrbf", pool.m_full_rbf);
return ret; return ret;
} }
@ -684,6 +685,7 @@ static RPCHelpMan getmempoolinfo()
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, {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, "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::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{ RPCExamples{
HelpExampleCli("getmempoolinfo", "") HelpExampleCli("getmempoolinfo", "")

View File

@ -458,6 +458,7 @@ CTxMemPool::CTxMemPool(const Options& opts)
minerPolicyEstimator{opts.estimator}, minerPolicyEstimator{opts.estimator},
m_max_size_bytes{opts.max_size_bytes}, m_max_size_bytes{opts.max_size_bytes},
m_expiry{opts.expiry}, m_expiry{opts.expiry},
m_full_rbf{opts.full_rbf},
m_limits{opts.limits} m_limits{opts.limits}
{ {
_clear(); //lock free clear _clear(); //lock free clear

View File

@ -568,6 +568,7 @@ public:
const int64_t m_max_size_bytes; const int64_t m_max_size_bytes;
const std::chrono::seconds m_expiry; const std::chrono::seconds m_expiry;
const bool m_full_rbf;
using Limits = kernel::MemPoolLimits; using Limits = kernel::MemPoolLimits;

View File

@ -740,7 +740,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Applications relying on first-seen mempool behavior should // Applications relying on first-seen mempool behavior should
// check all unconfirmed ancestors; otherwise an opt-in ancestor // check all unconfirmed ancestors; otherwise an opt-in ancestor
// might be replaced, causing removal of this descendant. // 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"); return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
} }

View File

@ -83,6 +83,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test replacement relay fee...") self.log.info("Running test replacement relay fee...")
self.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") self.log.info("Passed")
def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None): def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
@ -698,5 +701,33 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx.vout[0].nValue -= 1 tx.vout[0].nValue -= 1
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) 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__': if __name__ == '__main__':
ReplaceByFeeTest().main() ReplaceByFeeTest().main()