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
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)).

View file

@ -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
-------------------

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

View file

@ -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

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};
mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
ApplyArgsManOptions(argsman, mempool_opts.limits);
}

View file

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

View file

@ -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

View file

@ -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;

View file

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

View file

@ -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()