aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcoFalke <falke.marco@gmail.com>2021-06-08 17:00:18 +0200
committerMarcoFalke <falke.marco@gmail.com>2021-06-08 17:00:28 +0200
commit82bc7faec8079b50f248655a97950087948f065d (patch)
tree89d1ae736005e41b21f372afe398af05636f6bc1
parent45a8b01fad96d93113896b9332618e936aef77af (diff)
parent2eb0eeda39cab997f9a5f817f7c12e7fffeaf35a (diff)
downloadbitcoin-82bc7faec8079b50f248655a97950087948f065d.tar.xz
Merge bitcoin/bitcoin#21946: Document and test lack of inherited signaling in RBF policy
2eb0eeda39cab997f9a5f817f7c12e7fffeaf35a validation: document lack of inherited signaling in RBF policy (Antoine Riard) 906b6d9da6a6b2e6a5f1d9046b3b9c2c7e490c99 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: ACK 2eb0eeda39cab997f9a5f817f7c12e7fffeaf35a benthecarman: ACK 2eb0eeda39cab997f9a5f817f7c12e7fffeaf35a Tree-SHA512: d41453d3b49bae3c1eb532a968f43bc047084913bd285929d4d9cba142777ff2be38163d912e28dfc635f4ecf446de68effad799c6e71be52f81e83410c712fb
-rw-r--r--src/validation.cpp11
-rwxr-xr-xtest/functional/feature_rbf.py67
2 files changed, 74 insertions, 4 deletions
diff --git a/src/validation.cpp b/src/validation.cpp
index e6e6aadb17..5e3d429c2e 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -629,10 +629,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// is for the sake of multi-party protocols, where we don't
// want a single party to be able to disable replacement.
//
- // The opt-out ignores descendants as anyone relying on
- // first-seen mempool behavior should be checking all
- // unconfirmed ancestors anyway; doing otherwise is hopelessly
- // insecure.
+ // Transactions that don't explicitly signal replaceability are
+ // *not* replaceable with the current logic, even if one of their
+ // unconfirmed ancestors signals replaceability. This diverges
+ // 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;
for (const CTxIn &_txin : ptxConflicting->vin)
{
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 344db5f652..0bb04ae267 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -116,6 +116,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test prioritised transactions...")
self.test_prioritised_transactions()
+ self.log.info("Running test no inherited signaling...")
+ self.test_no_inherited_signaling()
+
self.log.info("Passed")
def test_simple_doublespend(self):
@@ -564,5 +567,69 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert_equal(json0["vin"][0]["sequence"], 4294967293)
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__':
ReplaceByFeeTest().main()