mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-07 00:40:41 +01:00
Merge bitcoin/bitcoin#21946: Document and test lack of inherited signaling in RBF policy
2eb0eeda39
validation: document lack of inherited signaling in RBF policy (Antoine Riard)906b6d9da6
test: Extend feature_rbf.py with no inherited signaling (Antoine Riard) Pull request description: Contrary to BIP125 or other full-node implementation (e.g btcd), Bitcoin Core's mempool policy doesn't implement inherited signaling. This PR documents our mempool behavior on this and add a test demonstrating the case. ACKs for top commit: jonatack: ACK2eb0eeda39
benthecarman: ACK2eb0eeda39
Tree-SHA512: d41453d3b49bae3c1eb532a968f43bc047084913bd285929d4d9cba142777ff2be38163d912e28dfc635f4ecf446de68effad799c6e71be52f81e83410c712fb
This commit is contained in:
commit
82bc7faec8
2 changed files with 74 additions and 4 deletions
|
@ -629,10 +629,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
// is for the sake of multi-party protocols, where we don't
|
// is for the sake of multi-party protocols, where we don't
|
||||||
// want a single party to be able to disable replacement.
|
// want a single party to be able to disable replacement.
|
||||||
//
|
//
|
||||||
// The opt-out ignores descendants as anyone relying on
|
// Transactions that don't explicitly signal replaceability are
|
||||||
// first-seen mempool behavior should be checking all
|
// *not* replaceable with the current logic, even if one of their
|
||||||
// unconfirmed ancestors anyway; doing otherwise is hopelessly
|
// unconfirmed ancestors signals replaceability. This diverges
|
||||||
// insecure.
|
// from BIP125's inherited signaling description (see CVE-2021-31876).
|
||||||
|
// 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.
|
||||||
bool fReplacementOptOut = true;
|
bool fReplacementOptOut = true;
|
||||||
for (const CTxIn &_txin : ptxConflicting->vin)
|
for (const CTxIn &_txin : ptxConflicting->vin)
|
||||||
{
|
{
|
||||||
|
|
|
@ -116,6 +116,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||||
self.log.info("Running test prioritised transactions...")
|
self.log.info("Running test prioritised transactions...")
|
||||||
self.test_prioritised_transactions()
|
self.test_prioritised_transactions()
|
||||||
|
|
||||||
|
self.log.info("Running test no inherited signaling...")
|
||||||
|
self.test_no_inherited_signaling()
|
||||||
|
|
||||||
self.log.info("Passed")
|
self.log.info("Passed")
|
||||||
|
|
||||||
def test_simple_doublespend(self):
|
def test_simple_doublespend(self):
|
||||||
|
@ -564,5 +567,69 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||||
assert_equal(json0["vin"][0]["sequence"], 4294967293)
|
assert_equal(json0["vin"][0]["sequence"], 4294967293)
|
||||||
assert_equal(json1["vin"][0]["sequence"], 4294967294)
|
assert_equal(json1["vin"][0]["sequence"], 4294967294)
|
||||||
|
|
||||||
|
def test_no_inherited_signaling(self):
|
||||||
|
# Send tx from which to conflict outputs later
|
||||||
|
base_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_blocks()
|
||||||
|
|
||||||
|
# Create an explicitly opt-in parent transaction
|
||||||
|
optin_parent_tx = self.nodes[0].createrawtransaction([{
|
||||||
|
'txid': base_txid,
|
||||||
|
'vout': 0,
|
||||||
|
"sequence": 0xfffffffd,
|
||||||
|
}], {self.nodes[0].getnewaddress(): Decimal("9.99998")})
|
||||||
|
|
||||||
|
optin_parent_tx = self.nodes[0].signrawtransactionwithwallet(optin_parent_tx)
|
||||||
|
|
||||||
|
# Broadcast parent tx
|
||||||
|
optin_parent_txid = self.nodes[0].sendrawtransaction(hexstring=optin_parent_tx["hex"], maxfeerate=0)
|
||||||
|
assert optin_parent_txid in self.nodes[0].getrawmempool()
|
||||||
|
|
||||||
|
replacement_parent_tx = self.nodes[0].createrawtransaction([{
|
||||||
|
'txid': base_txid,
|
||||||
|
'vout': 0,
|
||||||
|
"sequence": 0xfffffffd,
|
||||||
|
}], {self.nodes[0].getnewaddress(): Decimal("9.90000")})
|
||||||
|
|
||||||
|
replacement_parent_tx = self.nodes[0].signrawtransactionwithwallet(replacement_parent_tx)
|
||||||
|
|
||||||
|
# Test if parent tx can be replaced.
|
||||||
|
res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']], maxfeerate=0)[0]
|
||||||
|
|
||||||
|
# Parent can be replaced.
|
||||||
|
assert_equal(res['allowed'], True)
|
||||||
|
|
||||||
|
# Create an opt-out child tx spending the opt-in parent
|
||||||
|
optout_child_tx = self.nodes[0].createrawtransaction([{
|
||||||
|
'txid': optin_parent_txid,
|
||||||
|
'vout': 0,
|
||||||
|
"sequence": 0xffffffff,
|
||||||
|
}], {self.nodes[0].getnewaddress(): Decimal("9.99990")})
|
||||||
|
|
||||||
|
optout_child_tx = self.nodes[0].signrawtransactionwithwallet(optout_child_tx)
|
||||||
|
|
||||||
|
# Broadcast child tx
|
||||||
|
optout_child_txid = self.nodes[0].sendrawtransaction(hexstring=optout_child_tx["hex"], maxfeerate=0)
|
||||||
|
assert optout_child_txid in self.nodes[0].getrawmempool()
|
||||||
|
|
||||||
|
replacement_child_tx = self.nodes[0].createrawtransaction([{
|
||||||
|
'txid': optin_parent_txid,
|
||||||
|
'vout': 0,
|
||||||
|
"sequence": 0xffffffff,
|
||||||
|
}], {self.nodes[0].getnewaddress(): Decimal("9.00000")})
|
||||||
|
|
||||||
|
replacement_child_tx = self.nodes[0].signrawtransactionwithwallet(replacement_child_tx)
|
||||||
|
|
||||||
|
# Broadcast replacement child tx
|
||||||
|
# BIP 125 :
|
||||||
|
# 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
|
||||||
|
# Summary section.
|
||||||
|
# The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid`) does.
|
||||||
|
# The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
|
||||||
|
# See CVE-2021-31876 for further explanations.
|
||||||
|
assert optin_parent_txid in self.nodes[0].getrawmempool()
|
||||||
|
assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ReplaceByFeeTest().main()
|
ReplaceByFeeTest().main()
|
||||||
|
|
Loading…
Add table
Reference in a new issue