Skip to content

Commit 867f6af

Browse files
committed
Merge bitcoin/bitcoin#29873: policy: restrict all TRUC (v3) transactions to 10kvB
154b2b2 [fuzz] V3_MAX_VSIZE and effective ancestor/descendant size limits (glozow) a29f1df [policy] restrict all v3 transactions to 10kvB (glozow) d578e2e [policy] explicitly require non-v3 for CPFP carve out (glozow) Pull request description: Opening for discussion / conceptual review. We like the idea of a smaller maximum transaction size because: - It lowers potential replacement cost (i.e. harder to do Rule 3 pinning via gigantic transaction) - They are easier to bin-pack in block template production - They equate to a tighter memory limit in data structures that are bounded by a number of transactions (e.g. orphanage and vExtraTxnForCompact). For example, the current memory bounds for orphanage is 100KvB * 100 = 40MB, and guaranteeing 1 tx per peer would require reserving a pretty large space. History for `MAX_STANDARD_TX_WEIGHT=100KvB` (copied from bitcoin/bitcoin#29873 (comment)): - 2010-09-13 In bitcoin/bitcoin@3df6287 satoshi added a 100kB (MAX_BLOCK_SIZE_GEN/5 with MBS_GEN = MAX_BLOCK_SIZE/2) limit on new transactions in CreateTransaction() - 2013-02-04 bitcoin/bitcoin#2273 In gavin gave that constant a name, and made it apply to transaction relay as well Lowering `MAX_STANDARD_TX_WEIGHT` for all txns is not being proposed, as there are existing apps/protocols that rely on large transactions. However, it's been brought up that we should consider this for TRUCs (which is especially designed to avoid Rule 3 pinning). This reduction should be ok because using nVersion=3 isn't standard yet, so this wouldn't break somebody's existing use case. If we find that this is too small, we can always increase it later. Decreasing would be much more difficult. ~[Expected size of a commitment transaction](https://github.com/lightning/bolts/blob/master/03-transactions.md#expected-weight-of-the-commitment-transaction) is within (900 + 172 * 483 + 224) / 4 = 21050vB~ EDIT: this is incorrect, but perhaps not something that should affect how we choose this number. ACKs for top commit: sdaftuar: ACK 154b2b2 achow101: ACK 154b2b2 instagibbs: ACK 154b2b2 t-bast: ACK bitcoin/bitcoin@154b2b2 murchandamus: crACK 154b2b2 Tree-SHA512: 89392a460908a8ea9f547d90e00f5181de0eaa9d2c4f2766140a91294ade3229b3d181833cad9afc93a0d0e8c4b96ee2f5aeda7c50ad7e6f3a8320b9e0c5ae97
2 parents 6300438 + 154b2b2 commit 867f6af

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

src/policy/v3_policy.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
6767

6868
// Now we have all ancestors, so we can start checking v3 rules.
6969
if (ptx->nVersion == 3) {
70+
// SingleV3Checks should have checked this already.
71+
if (!Assume(vsize <= V3_MAX_VSIZE)) {
72+
return strprintf("v3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
73+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, V3_MAX_VSIZE);
74+
}
75+
7076
if (mempool_ancestors.size() + in_package_parents.size() + 1 > V3_ANCESTOR_LIMIT) {
7177
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
7278
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
@@ -186,6 +192,12 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
186192
// The rest of the rules only apply to transactions with nVersion=3.
187193
if (ptx->nVersion != 3) return std::nullopt;
188194

195+
if (vsize > V3_MAX_VSIZE) {
196+
return std::make_pair(strprintf("v3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
197+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, V3_MAX_VSIZE),
198+
nullptr);
199+
}
200+
189201
// Check that V3_ANCESTOR_LIMIT would not be violated.
190202
if (mempool_ancestors.size() + 1 > V3_ANCESTOR_LIMIT) {
191203
return std::make_pair(strprintf("tx %s (wtxid=%s) would have too many ancestors",

src/policy/v3_policy.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ static constexpr unsigned int V3_DESCENDANT_LIMIT{2};
2424
/** Maximum number of transactions including a V3 tx and all its mempool ancestors. */
2525
static constexpr unsigned int V3_ANCESTOR_LIMIT{2};
2626

27+
/** Maximum sigop-adjusted virtual size of all v3 transactions. */
28+
static constexpr int64_t V3_MAX_VSIZE{10000};
2729
/** Maximum sigop-adjusted virtual size of a tx which spends from an unconfirmed v3 transaction. */
2830
static constexpr int64_t V3_CHILD_MAX_VSIZE{1000};
2931
// These limits are within the default ancestor/descendant limits.
30-
static_assert(V3_CHILD_MAX_VSIZE + MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR <= DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000);
31-
static_assert(V3_CHILD_MAX_VSIZE + MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR <= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1000);
32+
static_assert(V3_MAX_VSIZE + V3_CHILD_MAX_VSIZE <= DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000);
33+
static_assert(V3_MAX_VSIZE + V3_CHILD_MAX_VSIZE <= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1000);
3234

3335
/** Must be called for every transaction, even if not v3. Not strictly necessary for transactions
3436
* accepted through AcceptMultipleTransactions.
@@ -40,6 +42,7 @@ static_assert(V3_CHILD_MAX_VSIZE + MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR
4042
* 4. A v3's descendant set, including itself, must be within V3_DESCENDANT_LIMIT.
4143
* 5. If a v3 tx has any unconfirmed ancestors, the tx's sigop-adjusted vsize must be within
4244
* V3_CHILD_MAX_VSIZE.
45+
* 6. A v3 tx must be within V3_MAX_VSIZE.
4346
*
4447
*
4548
* @param[in] mempool_ancestors The in-mempool ancestors of ptx.

src/test/util/txmempool.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,15 @@ void CheckMempoolV3Invariants(const CTxMemPool& tx_pool)
119119
for (const auto& tx_info : tx_pool.infoAll()) {
120120
const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
121121
if (tx_info.tx->nVersion == 3) {
122+
// Check that special maximum virtual size is respected
123+
Assert(entry.GetTxSize() <= V3_MAX_VSIZE);
124+
122125
// Check that special v3 ancestor/descendant limits and rules are always respected
123126
Assert(entry.GetCountWithDescendants() <= V3_DESCENDANT_LIMIT);
124127
Assert(entry.GetCountWithAncestors() <= V3_ANCESTOR_LIMIT);
128+
Assert(entry.GetSizeWithDescendants() <= V3_MAX_VSIZE + V3_CHILD_MAX_VSIZE);
129+
Assert(entry.GetSizeWithAncestors() <= V3_MAX_VSIZE + V3_CHILD_MAX_VSIZE);
130+
125131
// If this transaction has at least 1 ancestor, it's a "child" and has restricted weight.
126132
if (entry.GetCountWithAncestors() > 1) {
127133
Assert(entry.GetTxSize() <= V3_CHILD_MAX_VSIZE);

src/validation.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
951951
// If the new transaction is relatively small (up to 40k weight)
952952
// and has at most one ancestor (ie ancestor limit of 2, including
953953
// the new transaction), allow it if its parent has exactly the
954-
// descendant limit descendants.
954+
// descendant limit descendants. The transaction also cannot be v3,
955+
// as its topology restrictions do not allow a second child.
955956
//
956957
// This allows protocols which rely on distrusting counterparties
957958
// being able to broadcast descendants of an unconfirmed transaction
@@ -965,7 +966,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
965966
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
966967
};
967968
const auto error_message{util::ErrorString(ancestors).original};
968-
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT) {
969+
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == 3) {
969970
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
970971
}
971972
if (auto ancestors_retry{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) {

test/functional/mempool_accept_v3.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from test_framework.messages import (
88
MAX_BIP125_RBF_SEQUENCE,
9+
WITNESS_SCALE_FACTOR,
910
)
1011
from test_framework.test_framework import BitcoinTestFramework
1112
from test_framework.util import (
@@ -21,6 +22,7 @@
2122
)
2223

2324
MAX_REPLACEMENT_CANDIDATES = 100
25+
V3_MAX_VSIZE = 10000
2426

2527
def cleanup(extra_args=None):
2628
def decorator(func):
@@ -49,6 +51,20 @@ def check_mempool(self, txids):
4951
assert_equal(len(txids), len(mempool_contents))
5052
assert all([txid in txids for txid in mempool_contents])
5153

54+
@cleanup(extra_args=["-datacarriersize=20000", "-acceptnonstdtxn=1"])
55+
def test_v3_max_vsize(self):
56+
node = self.nodes[0]
57+
self.log.info("Test v3-specific maximum transaction vsize")
58+
tx_v3_heavy = self.wallet.create_self_transfer(target_weight=(V3_MAX_VSIZE + 1) * WITNESS_SCALE_FACTOR, version=3)
59+
assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), V3_MAX_VSIZE)
60+
expected_error_heavy = f"v3-rule-violation, v3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
61+
assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])
62+
self.check_mempool([])
63+
64+
# Ensure we are hitting the v3-specific limit and not something else
65+
tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_weight=(V3_MAX_VSIZE + 1) * WITNESS_SCALE_FACTOR, version=2)
66+
self.check_mempool([tx_v2_heavy["txid"]])
67+
5268
@cleanup(extra_args=["-datacarriersize=1000", "-acceptnonstdtxn=1"])
5369
def test_v3_acceptance(self):
5470
node = self.nodes[0]
@@ -201,21 +217,47 @@ def test_nondefault_package_limits(self):
201217
"""
202218
node = self.nodes[0]
203219
self.log.info("Test that a decreased limitdescendantsize also applies to v3 child")
204-
tx_v3_parent_large1 = self.wallet.send_self_transfer(from_node=node, target_weight=99900, version=3)
205-
tx_v3_child_large1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent_large1["new_utxo"], version=3)
206-
# Child is within v3 limits, but parent's descendant limit is exceeded
207-
assert_greater_than(1000, tx_v3_child_large1["tx"].get_vsize())
220+
parent_target_weight = 9990 * WITNESS_SCALE_FACTOR
221+
child_target_weight = 500 * WITNESS_SCALE_FACTOR
222+
tx_v3_parent_large1 = self.wallet.send_self_transfer(
223+
from_node=node,
224+
target_weight=parent_target_weight,
225+
version=3
226+
)
227+
tx_v3_child_large1 = self.wallet.create_self_transfer(
228+
utxo_to_spend=tx_v3_parent_large1["new_utxo"],
229+
target_weight=child_target_weight,
230+
version=3
231+
)
232+
233+
# Parent and child are within v3 limits, but parent's 10kvB descendant limit is exceeded
234+
assert_greater_than_or_equal(V3_MAX_VSIZE, tx_v3_parent_large1["tx"].get_vsize())
235+
assert_greater_than_or_equal(1000, tx_v3_child_large1["tx"].get_vsize())
236+
assert_greater_than(tx_v3_parent_large1["tx"].get_vsize() + tx_v3_child_large1["tx"].get_vsize(), 10000)
237+
208238
assert_raises_rpc_error(-26, f"too-long-mempool-chain, exceeds descendant size limit for tx {tx_v3_parent_large1['txid']}", node.sendrawtransaction, tx_v3_child_large1["hex"])
209239
self.check_mempool([tx_v3_parent_large1["txid"]])
210240
assert_equal(node.getmempoolentry(tx_v3_parent_large1["txid"])["descendantcount"], 1)
211241
self.generate(node, 1)
212242

213243
self.log.info("Test that a decreased limitancestorsize also applies to v3 parent")
214244
self.restart_node(0, extra_args=["-limitancestorsize=10", "-datacarriersize=40000", "-acceptnonstdtxn=1"])
215-
tx_v3_parent_large2 = self.wallet.send_self_transfer(from_node=node, target_weight=99900, version=3)
216-
tx_v3_child_large2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent_large2["new_utxo"], version=3)
217-
# Child is within v3 limits
245+
tx_v3_parent_large2 = self.wallet.send_self_transfer(
246+
from_node=node,
247+
target_weight=parent_target_weight,
248+
version=3
249+
)
250+
tx_v3_child_large2 = self.wallet.create_self_transfer(
251+
utxo_to_spend=tx_v3_parent_large2["new_utxo"],
252+
target_weight=child_target_weight,
253+
version=3
254+
)
255+
256+
# Parent and child are within v3 limits
257+
assert_greater_than_or_equal(V3_MAX_VSIZE, tx_v3_parent_large2["tx"].get_vsize())
218258
assert_greater_than_or_equal(1000, tx_v3_child_large2["tx"].get_vsize())
259+
assert_greater_than(tx_v3_parent_large2["tx"].get_vsize() + tx_v3_child_large2["tx"].get_vsize(), 10000)
260+
219261
assert_raises_rpc_error(-26, f"too-long-mempool-chain, exceeds ancestor size limit", node.sendrawtransaction, tx_v3_child_large2["hex"])
220262
self.check_mempool([tx_v3_parent_large2["txid"]])
221263

@@ -585,6 +627,7 @@ def run_test(self):
585627
node = self.nodes[0]
586628
self.wallet = MiniWallet(node)
587629
self.generate(self.wallet, 120)
630+
self.test_v3_max_vsize()
588631
self.test_v3_acceptance()
589632
self.test_v3_replacement()
590633
self.test_v3_bip125()

0 commit comments

Comments
 (0)