Skip to content

Commit eb8d5a2

Browse files
glozowsdaftuar
andcommitted
[policy] add v3 policy rules
Co-authored-by: Suhas Daftuar <[email protected]>
1 parent 9a29d47 commit eb8d5a2

File tree

4 files changed

+596
-0
lines changed

4 files changed

+596
-0
lines changed

src/Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ BITCOIN_CORE_H = \
238238
node/validation_cache_args.h \
239239
noui.h \
240240
outputtype.h \
241+
policy/v3_policy.h \
241242
policy/feerate.h \
242243
policy/fees.h \
243244
policy/fees_args.h \
@@ -439,6 +440,7 @@ libbitcoin_node_a_SOURCES = \
439440
node/utxo_snapshot.cpp \
440441
node/validation_cache_args.cpp \
441442
noui.cpp \
443+
policy/v3_policy.cpp \
442444
policy/fees.cpp \
443445
policy/fees_args.cpp \
444446
policy/packages.cpp \
@@ -694,6 +696,7 @@ libbitcoin_common_a_SOURCES = \
694696
netbase.cpp \
695697
net_permissions.cpp \
696698
outputtype.cpp \
699+
policy/v3_policy.cpp \
697700
policy/feerate.cpp \
698701
policy/policy.cpp \
699702
protocol.cpp \
@@ -953,6 +956,7 @@ libbitcoinkernel_la_SOURCES = \
953956
node/blockstorage.cpp \
954957
node/chainstate.cpp \
955958
node/utxo_snapshot.cpp \
959+
policy/v3_policy.cpp \
956960
policy/feerate.cpp \
957961
policy/packages.cpp \
958962
policy/policy.cpp \

src/policy/v3_policy.cpp

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <policy/v3_policy.h>
6+
7+
#include <coins.h>
8+
#include <consensus/amount.h>
9+
#include <logging.h>
10+
#include <tinyformat.h>
11+
#include <util/check.h>
12+
13+
#include <algorithm>
14+
#include <numeric>
15+
#include <vector>
16+
17+
/** Helper for PackageV3Checks: Returns a vector containing the indices of transactions (within
18+
* package) that are direct parents of ptx. */
19+
std::vector<size_t> FindInPackageParents(const Package& package, const CTransactionRef& ptx)
20+
{
21+
std::vector<size_t> in_package_parents;
22+
23+
std::set<Txid> possible_parents;
24+
for (auto &input : ptx->vin) {
25+
possible_parents.insert(input.prevout.hash);
26+
}
27+
28+
for (size_t i{0}; i < package.size(); ++i) {
29+
const auto& tx = package.at(i);
30+
// We assume the package is sorted, so that we don't need to continue
31+
// looking past the transaction itself.
32+
if (&(*tx) == &(*ptx)) break;
33+
if (possible_parents.count(tx->GetHash())) {
34+
in_package_parents.push_back(i);
35+
}
36+
}
37+
return in_package_parents;
38+
}
39+
40+
/** Helper for PackageV3Checks, storing info for a mempool or package parent. */
41+
struct ParentInfo {
42+
/** Txid used to identify this parent by prevout */
43+
const Txid& m_txid;
44+
/** Wtxid used for debug string */
45+
const Wtxid& m_wtxid;
46+
/** nVersion used to check inheritance of v3 and non-v3 */
47+
decltype(CTransaction::nVersion) m_version;
48+
/** If parent is in mempool, whether it has any descendants in mempool. */
49+
bool m_has_mempool_descendant;
50+
51+
ParentInfo() = delete;
52+
ParentInfo(const Txid& txid, const Wtxid& wtxid, decltype(CTransaction::nVersion) version, bool has_mempool_descendant) :
53+
m_txid{txid}, m_wtxid{wtxid}, m_version{version},
54+
m_has_mempool_descendant{has_mempool_descendant}
55+
{}
56+
};
57+
58+
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize,
59+
const Package& package,
60+
const CTxMemPool::setEntries& mempool_ancestors)
61+
{
62+
// This function is specialized for these limits, and must be reimplemented if they ever change.
63+
static_assert(V3_ANCESTOR_LIMIT == 2);
64+
static_assert(V3_DESCENDANT_LIMIT == 2);
65+
66+
const auto in_package_parents{FindInPackageParents(package, ptx)};
67+
68+
// Now we have all ancestors, so we can start checking v3 rules.
69+
if (ptx->nVersion == 3) {
70+
if (mempool_ancestors.size() + in_package_parents.size() + 1 > V3_ANCESTOR_LIMIT) {
71+
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
72+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
73+
}
74+
75+
const bool has_parent{mempool_ancestors.size() + in_package_parents.size() > 0};
76+
if (has_parent) {
77+
// A v3 child cannot be too large.
78+
if (vsize > V3_CHILD_MAX_VSIZE) {
79+
return strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
80+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
81+
vsize, V3_CHILD_MAX_VSIZE);
82+
}
83+
84+
const auto parent_info = [&] {
85+
if (mempool_ancestors.size() > 0) {
86+
// There's a parent in the mempool.
87+
auto& mempool_parent = *mempool_ancestors.begin();
88+
Assume(mempool_parent->GetCountWithDescendants() == 1);
89+
return ParentInfo{mempool_parent->GetTx().GetHash(),
90+
mempool_parent->GetTx().GetWitnessHash(),
91+
mempool_parent->GetTx().nVersion,
92+
/*has_mempool_descendant=*/mempool_parent->GetCountWithDescendants() > 1};
93+
} else {
94+
// Ancestor must be in the package. Find it.
95+
auto& parent_index = in_package_parents.front();
96+
auto& package_parent = package.at(parent_index);
97+
return ParentInfo{package_parent->GetHash(),
98+
package_parent->GetWitnessHash(),
99+
package_parent->nVersion,
100+
/*has_mempool_descendant=*/false};
101+
}
102+
}();
103+
104+
// If there is a parent, it must have the right version.
105+
if (parent_info.m_version != 3) {
106+
return strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
107+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
108+
parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString());
109+
}
110+
111+
for (const auto& package_tx : package) {
112+
// Skip same tx.
113+
if (&(*package_tx) == &(*ptx)) continue;
114+
115+
for (auto& input : package_tx->vin) {
116+
// Fail if we find another tx with the same parent. We don't check whether the
117+
// sibling is to-be-replaced (done in SingleV3Checks) because these transactions
118+
// are within the same package.
119+
if (input.prevout.hash == parent_info.m_txid) {
120+
return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
121+
parent_info.m_txid.ToString(),
122+
parent_info.m_wtxid.ToString());
123+
}
124+
125+
// This tx can't have both a parent and an in-package child.
126+
if (input.prevout.hash == ptx->GetHash()) {
127+
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
128+
package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString());
129+
}
130+
}
131+
}
132+
133+
// It shouldn't be possible to have any mempool siblings at this point. SingleV3Checks
134+
// catches mempool siblings. Also, if the package consists of connected transactions,
135+
// any tx having a mempool ancestor would mean the package exceeds ancestor limits.
136+
if (!Assume(!parent_info.m_has_mempool_descendant)) {
137+
return strprintf("tx %u would exceed descendant count limit", parent_info.m_wtxid.ToString());
138+
}
139+
}
140+
} else {
141+
// Non-v3 transactions cannot have v3 parents.
142+
for (auto it : mempool_ancestors) {
143+
if (it->GetTx().nVersion == 3) {
144+
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
145+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
146+
it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString());
147+
}
148+
}
149+
for (const auto& index: in_package_parents) {
150+
if (package.at(index)->nVersion == 3) {
151+
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
152+
ptx->GetHash().ToString(),
153+
ptx->GetWitnessHash().ToString(),
154+
package.at(index)->GetHash().ToString(),
155+
package.at(index)->GetWitnessHash().ToString());
156+
}
157+
}
158+
}
159+
return std::nullopt;
160+
}
161+
162+
std::optional<std::string> SingleV3Checks(const CTransactionRef& ptx,
163+
const CTxMemPool::setEntries& mempool_ancestors,
164+
const std::set<Txid>& direct_conflicts,
165+
int64_t vsize)
166+
{
167+
// Check v3 and non-v3 inheritance.
168+
for (const auto& entry : mempool_ancestors) {
169+
if (ptx->nVersion != 3 && entry->GetTx().nVersion == 3) {
170+
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
171+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
172+
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString());
173+
} else if (ptx->nVersion == 3 && entry->GetTx().nVersion != 3) {
174+
return strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
175+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
176+
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString());
177+
}
178+
}
179+
180+
// This function is specialized for these limits, and must be reimplemented if they ever change.
181+
static_assert(V3_ANCESTOR_LIMIT == 2);
182+
static_assert(V3_DESCENDANT_LIMIT == 2);
183+
184+
// The rest of the rules only apply to transactions with nVersion=3.
185+
if (ptx->nVersion != 3) return std::nullopt;
186+
187+
// Check that V3_ANCESTOR_LIMIT would not be violated, including both in-package and in-mempool.
188+
if (mempool_ancestors.size() + 1 > V3_ANCESTOR_LIMIT) {
189+
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
190+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
191+
}
192+
193+
// Remaining checks only pertain to transactions with unconfirmed ancestors.
194+
if (mempool_ancestors.size() > 0) {
195+
// If this transaction spends V3 parents, it cannot be too large.
196+
if (vsize > V3_CHILD_MAX_VSIZE) {
197+
return strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
198+
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, V3_CHILD_MAX_VSIZE);
199+
}
200+
201+
// Check the descendant counts of in-mempool ancestors.
202+
const auto& parent_entry = *mempool_ancestors.begin();
203+
// If there are any ancestors, this is the only child allowed. The parent cannot have any
204+
// other descendants. We handle the possibility of multiple children as that case is
205+
// possible through a reorg.
206+
const auto& children = parent_entry->GetMemPoolChildrenConst();
207+
// Don't double-count a transaction that is going to be replaced. This logic assumes that
208+
// any descendant of the V3 transaction is a direct child, which makes sense because a V3
209+
// transaction can only have 1 descendant.
210+
const bool child_will_be_replaced = !children.empty() &&
211+
std::any_of(children.cbegin(), children.cend(),
212+
[&direct_conflicts](const CTxMemPoolEntry& child){return direct_conflicts.count(child.GetTx().GetHash()) > 0;});
213+
if (parent_entry->GetCountWithDescendants() + 1 > V3_DESCENDANT_LIMIT && !child_will_be_replaced) {
214+
return strprintf("tx %u (wtxid=%s) would exceed descendant count limit",
215+
parent_entry->GetSharedTx()->GetHash().ToString(),
216+
parent_entry->GetSharedTx()->GetWitnessHash().ToString());
217+
}
218+
}
219+
return std::nullopt;
220+
}

src/policy/v3_policy.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_POLICY_V3_POLICY_H
6+
#define BITCOIN_POLICY_V3_POLICY_H
7+
8+
#include <consensus/amount.h>
9+
#include <policy/packages.h>
10+
#include <policy/policy.h>
11+
#include <primitives/transaction.h>
12+
#include <txmempool.h>
13+
#include <util/result.h>
14+
15+
#include <set>
16+
#include <string>
17+
18+
// This module enforces rules for transactions with nVersion=3 ("v3 transactions") which help make
19+
// RBF abilities more robust.
20+
21+
// v3 only allows 1 parent and 1 child when unconfirmed.
22+
/** Maximum number of transactions including an unconfirmed tx and its descendants. */
23+
static constexpr unsigned int V3_DESCENDANT_LIMIT{2};
24+
/** Maximum number of transactions including a V3 tx and all its mempool ancestors. */
25+
static constexpr unsigned int V3_ANCESTOR_LIMIT{2};
26+
27+
/** Maximum sigop-adjusted virtual size of a tx which spends from an unconfirmed v3 transaction. */
28+
static constexpr int64_t V3_CHILD_MAX_VSIZE{1000};
29+
// 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+
33+
/** Must be called for every transaction, even if not v3. Not strictly necessary for transactions
34+
* accepted through AcceptMultipleTransactions.
35+
*
36+
* Checks the following rules:
37+
* 1. A v3 tx must only have v3 unconfirmed ancestors.
38+
* 2. A non-v3 tx must only have non-v3 unconfirmed ancestors.
39+
* 3. A v3's ancestor set, including itself, must be within V3_ANCESTOR_LIMIT.
40+
* 4. A v3's descendant set, including itself, must be within V3_DESCENDANT_LIMIT.
41+
* 5. If a v3 tx has any unconfirmed ancestors, the tx's sigop-adjusted vsize must be within
42+
* V3_CHILD_MAX_VSIZE.
43+
*
44+
*
45+
* @param[in] mempool_ancestors The in-mempool ancestors of ptx.
46+
* @param[in] direct_conflicts In-mempool transactions this tx conflicts with. These conflicts
47+
* are used to more accurately calculate the resulting descendant
48+
* count of in-mempool ancestors.
49+
* @param[in] vsize The sigop-adjusted virtual size of ptx.
50+
*
51+
* @returns debug string if an error occurs, std::nullopt otherwise.
52+
*/
53+
std::optional<std::string> SingleV3Checks(const CTransactionRef& ptx,
54+
const CTxMemPool::setEntries& mempool_ancestors,
55+
const std::set<Txid>& direct_conflicts,
56+
int64_t vsize);
57+
58+
/** Must be called for every transaction that is submitted within a package, even if not v3.
59+
*
60+
* For each transaction in a package:
61+
* If it's not a v3 transaction, verify it has no direct v3 parents in the mempool or the package.
62+
63+
* If it is a v3 transaction, verify that any direct parents in the mempool or the package are v3.
64+
* If such a parent exists, verify that parent has no other children in the package or the mempool,
65+
* and that the transaction itself has no children in the package.
66+
*
67+
* If any v3 violations in the package exist, this test will fail for one of them:
68+
* - if a v3 transaction T has a parent in the mempool and a child in the package, then PV3C(T) will fail
69+
* - if a v3 transaction T has a parent in the package and a child in the package, then PV3C(T) will fail
70+
* - if a v3 transaction T and a v3 (sibling) transaction U have some parent in the mempool,
71+
* then PV3C(T) and PV3C(U) will fail
72+
* - if a v3 transaction T and a v3 (sibling) transaction U have some parent in the package,
73+
* then PV3C(T) and PV3C(U) will fail
74+
* - if a v3 transaction T has a parent P and a grandparent G in the package, then
75+
* PV3C(P) will fail (though PV3C(G) and PV3C(T) might succeed).
76+
*
77+
* @returns debug string if an error occurs, std::nullopt otherwise.
78+
* */
79+
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize,
80+
const Package& package,
81+
const CTxMemPool::setEntries& mempool_ancestors);
82+
83+
#endif // BITCOIN_POLICY_V3_POLICY_H

0 commit comments

Comments
 (0)