Skip to content

Commit 37fdf5a

Browse files
committed
Merge bitcoin/bitcoin#29424: v3 followups
6b161cb [test] second child of a v3 tx can be replaced individually (glozow) 5c998a6 [refactor] use MAX_PUBKEYS_PER_MULTISIG instead of magic numbers in test (glozow) a934642 [test] PackageV3Checks with inheritance violation in mempool ancestor (glozow) 63b62e1 [doc] fix docs and comments from v3 (glozow) Pull request description: Addresses final comments from #28948: - thread at bitcoin/bitcoin#28948 (comment), using ismaelsadeeq/bitcoin@87fc7f0 with some modifications - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) - bitcoin/bitcoin#28948 (comment) ACKs for top commit: instagibbs: ACK 6b161cb sdaftuar: utACK 6b161cb Tree-SHA512: 584fce7810f4d704ee6ab51fdc7d42bab342140cae3d076f89b5e1966dd1dd8293cb25b3121e41a4dcd65f9d4a735102b9ab2e90f98aa770b84e21f4d35d63d3
2 parents 3054416 + 6b161cb commit 37fdf5a

File tree

6 files changed

+44
-10
lines changed

6 files changed

+44
-10
lines changed

doc/policy/mempool-replacements.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ other consensus and policy rules, each of the following conditions are met:
1212

1313
1. The directly conflicting transactions all signal replaceability explicitly. A transaction is
1414
signaling BIP125 replaceability if any of its inputs have an nSequence number less than (0xffffffff - 1).
15-
A transaction also signals replaceibility if its nVersion field is set to 3.
15+
A transaction also signals replaceability if its nVersion field is set to 3.
1616

1717
*Rationale*: See [BIP125
1818
explanation](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#motivation).

src/policy/v3_policy.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,16 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
8181
vsize, V3_CHILD_MAX_VSIZE);
8282
}
8383

84+
// Exactly 1 parent exists, either in mempool or package. Find it.
8485
const auto parent_info = [&] {
8586
if (mempool_ancestors.size() > 0) {
86-
// There's a parent in the mempool.
8787
auto& mempool_parent = *mempool_ancestors.begin();
8888
Assume(mempool_parent->GetCountWithDescendants() == 1);
8989
return ParentInfo{mempool_parent->GetTx().GetHash(),
9090
mempool_parent->GetTx().GetWitnessHash(),
9191
mempool_parent->GetTx().nVersion,
9292
/*has_mempool_descendant=*/mempool_parent->GetCountWithDescendants() > 1};
9393
} else {
94-
// Ancestor must be in the package. Find it.
9594
auto& parent_index = in_package_parents.front();
9695
auto& package_parent = package.at(parent_index);
9796
return ParentInfo{package_parent->GetHash(),
@@ -184,7 +183,7 @@ std::optional<std::string> SingleV3Checks(const CTransactionRef& ptx,
184183
// The rest of the rules only apply to transactions with nVersion=3.
185184
if (ptx->nVersion != 3) return std::nullopt;
186185

187-
// Check that V3_ANCESTOR_LIMIT would not be violated, including both in-package and in-mempool.
186+
// Check that V3_ANCESTOR_LIMIT would not be violated.
188187
if (mempool_ancestors.size() + 1 > V3_ANCESTOR_LIMIT) {
189188
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
190189
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());

src/test/fuzz/tx_pool.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,6 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
407407
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
408408
if (accepted) {
409409
txids.push_back(tx->GetHash());
410-
// Only check fees if accepted and not bypass_limits, otherwise it's not guaranteed that
411-
// trimming has happened for this tx and previous iterations.
412410
CheckMempoolV3Invariants(tx_pool);
413411
}
414412
}

src/test/txvalidation_tests.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
119119

120120
Package package_v3_v2{mempool_tx_v3, tx_v2_from_v3};
121121
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), package_v3_v2, empty_ancestors), expected_error_str);
122+
CTxMemPool::setEntries entries_mempool_v3{pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value()};
123+
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), {tx_v2_from_v3}, entries_mempool_v3), expected_error_str);
122124

123125
// mempool_tx_v3 mempool_tx_v2
124126
// ^ ^
@@ -149,6 +151,8 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
149151

150152
Package package_v2_v3{mempool_tx_v2, tx_v3_from_v2};
151153
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), package_v2_v3, empty_ancestors), expected_error_str);
154+
CTxMemPool::setEntries entries_mempool_v2{pool.GetIter(mempool_tx_v2->GetHash().ToUint256()).value()};
155+
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), {tx_v3_from_v2}, entries_mempool_v2), expected_error_str);
152156

153157
// mempool_tx_v3 mempool_tx_v2
154158
// ^ ^

test/functional/mempool_accept_v3.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright (c) 2024 The Bitcoin Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
from decimal import Decimal
56

67
from test_framework.messages import (
78
MAX_BIP125_RBF_SEQUENCE,
@@ -397,6 +398,37 @@ def test_v3_in_testmempoolaccept(self):
397398
test_accept_2children_with_in_mempool_parent = node.testmempoolaccept([tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
398399
assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_in_mempool_parent])
399400

401+
@cleanup(extra_args=["-acceptnonstdtxn=1"])
402+
def test_reorg_2child_rbf(self):
403+
node = self.nodes[0]
404+
self.log.info("Test that children of a v3 transaction can be replaced individually, even if there are multiple due to reorg")
405+
406+
ancestor_tx = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
407+
self.check_mempool([ancestor_tx["txid"]])
408+
409+
block = self.generate(node, 1)[0]
410+
self.check_mempool([])
411+
412+
child_1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0])
413+
child_2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][1])
414+
self.check_mempool([child_1["txid"], child_2["txid"]])
415+
416+
self.generate(node, 1)
417+
self.check_mempool([])
418+
419+
# Create a reorg, causing ancestor_tx to exceed the 1-child limit
420+
node.invalidateblock(block)
421+
self.check_mempool([ancestor_tx["txid"], child_1["txid"], child_2["txid"]])
422+
assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
423+
424+
# Create a replacement of child_1. It does not conflict with child_2.
425+
child_1_conflict = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0], fee_rate=Decimal("0.01"))
426+
427+
# Ensure child_1 and child_1_conflict are different transactions
428+
assert (child_1_conflict["txid"] != child_1["txid"])
429+
self.check_mempool([ancestor_tx["txid"], child_1_conflict["txid"], child_2["txid"]])
430+
assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
431+
400432
def run_test(self):
401433
self.log.info("Generate blocks to create UTXOs")
402434
node = self.nodes[0]
@@ -412,6 +444,7 @@ def run_test(self):
412444
self.test_mempool_sibling()
413445
self.test_v3_package_inheritance()
414446
self.test_v3_in_testmempoolaccept()
447+
self.test_reorg_2child_rbf()
415448

416449

417450
if __name__ == "__main__":

test/functional/mempool_sigoplimit.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from test_framework.wallet_util import generate_keypair
4040

4141
DEFAULT_BYTES_PER_SIGOP = 20 # default setting
42-
42+
MAX_PUBKEYS_PER_MULTISIG = 20
4343

4444
class BytesPerSigOpTest(BitcoinTestFramework):
4545
def set_test_params(self):
@@ -159,13 +159,13 @@ def create_bare_multisig_tx(utxo_to_spend=None):
159159
# Separately, the parent tx is ok
160160
parent_individual_testres = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex()])[0]
161161
assert parent_individual_testres["allowed"]
162-
# Multisig is counted as MAX_PUBKEYS_PER_MULTISIG = 20 sigops
163-
assert_equal(parent_individual_testres["vsize"], 5000 * 20)
162+
max_multisig_vsize = MAX_PUBKEYS_PER_MULTISIG * 5000
163+
assert_equal(parent_individual_testres["vsize"], max_multisig_vsize)
164164

165165
# But together, it's exceeding limits in the *package* context. If sigops adjusted vsize wasn't being checked
166166
# here, it would get further in validation and give too-long-mempool-chain error instead.
167167
packet_test = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex(), tx_child.serialize().hex()])
168-
expected_package_error = f"package-mempool-limits, package size {2*20*5000} exceeds ancestor size limit [limit: 101000]"
168+
expected_package_error = f"package-mempool-limits, package size {2*max_multisig_vsize} exceeds ancestor size limit [limit: 101000]"
169169
assert_equal([x["package-error"] for x in packet_test], [expected_package_error] * 2)
170170

171171
# When we actually try to submit, the parent makes it into the mempool, but the child would exceed ancestor vsize limits

0 commit comments

Comments
 (0)