/bitcoin/src/policy/truc_policy.cpp
Line | Count | Source |
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/truc_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 PackageTRUCChecks: 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 | 2.46k | { |
21 | 2.46k | std::vector<size_t> in_package_parents; |
22 | | |
23 | 2.46k | std::set<Txid> possible_parents; |
24 | 5.64k | for (auto &input : ptx->vin) { Branch (24:22): [True: 5.64k, False: 2.46k]
|
25 | 5.64k | possible_parents.insert(input.prevout.hash); |
26 | 5.64k | } |
27 | | |
28 | 3.68k | for (size_t i{0}; i < package.size(); ++i) { Branch (28:23): [True: 3.68k, False: 0]
|
29 | 3.68k | 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 | 3.68k | if (&(*tx) == &(*ptx)) break; Branch (32:13): [True: 2.46k, False: 1.22k]
|
33 | 1.22k | if (possible_parents.count(tx->GetHash())) { Branch (33:13): [True: 1.22k, False: 0]
|
34 | 1.22k | in_package_parents.push_back(i); |
35 | 1.22k | } |
36 | 1.22k | } |
37 | 2.46k | return in_package_parents; |
38 | 2.46k | } |
39 | | |
40 | | /** Helper for PackageTRUCChecks, 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 | | /** version used to check inheritance of TRUC and non-TRUC */ |
47 | | decltype(CTransaction::version) 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::version) version, bool has_mempool_descendant) : |
53 | 120 | m_txid{txid}, m_wtxid{wtxid}, m_version{version}, |
54 | 120 | m_has_mempool_descendant{has_mempool_descendant} |
55 | 120 | {} |
56 | | }; |
57 | | |
58 | | std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize, |
59 | | const Package& package, |
60 | | const CTxMemPool::setEntries& mempool_ancestors) |
61 | 2.46k | { |
62 | | // This function is specialized for these limits, and must be reimplemented if they ever change. |
63 | 2.46k | static_assert(TRUC_ANCESTOR_LIMIT == 2); |
64 | 2.46k | static_assert(TRUC_DESCENDANT_LIMIT == 2); |
65 | | |
66 | 2.46k | const auto in_package_parents{FindInPackageParents(package, ptx)}; |
67 | | |
68 | | // Now we have all ancestors, so we can start checking TRUC rules. |
69 | 2.46k | if (ptx->version == TRUC_VERSION) { Branch (69:9): [True: 267, False: 2.19k]
|
70 | | // SingleTRUCChecks should have checked this already. |
71 | 267 | if (!Assume(vsize <= TRUC_MAX_VSIZE)) { Branch (71:13): [True: 0, False: 267]
|
72 | 0 | return strprintf("version=3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
73 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_MAX_VSIZE); |
74 | 0 | } |
75 | | |
76 | 267 | if (mempool_ancestors.size() + in_package_parents.size() + 1 > TRUC_ANCESTOR_LIMIT) { Branch (76:13): [True: 0, False: 267]
|
77 | 0 | return strprintf("tx %s (wtxid=%s) would have too many ancestors", |
78 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()); |
79 | 0 | } |
80 | | |
81 | 267 | const bool has_parent{mempool_ancestors.size() + in_package_parents.size() > 0}; |
82 | 267 | if (has_parent) { Branch (82:13): [True: 127, False: 140]
|
83 | | // A TRUC child cannot be too large. |
84 | 127 | if (vsize > TRUC_CHILD_MAX_VSIZE) { Branch (84:17): [True: 7, False: 120]
|
85 | 7 | return strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
86 | 7 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
87 | 7 | vsize, TRUC_CHILD_MAX_VSIZE); |
88 | 7 | } |
89 | | |
90 | | // Exactly 1 parent exists, either in mempool or package. Find it. |
91 | 120 | const auto parent_info = [&] { |
92 | 120 | if (mempool_ancestors.size() > 0) { Branch (92:21): [True: 18, False: 102]
|
93 | 18 | auto& mempool_parent = *mempool_ancestors.begin(); |
94 | 18 | return ParentInfo{mempool_parent->GetTx().GetHash(), |
95 | 18 | mempool_parent->GetTx().GetWitnessHash(), |
96 | 18 | mempool_parent->GetTx().version, |
97 | 18 | /*has_mempool_descendant=*/mempool_parent->GetCountWithDescendants() > 1}; |
98 | 102 | } else { |
99 | 102 | auto& parent_index = in_package_parents.front(); |
100 | 102 | auto& package_parent = package.at(parent_index); |
101 | 102 | return ParentInfo{package_parent->GetHash(), |
102 | 102 | package_parent->GetWitnessHash(), |
103 | 102 | package_parent->version, |
104 | 102 | /*has_mempool_descendant=*/false}; |
105 | 102 | } |
106 | 120 | }(); |
107 | | |
108 | | // If there is a parent, it must have the right version. |
109 | 120 | if (parent_info.m_version != TRUC_VERSION) { Branch (109:17): [True: 34, False: 86]
|
110 | 34 | return strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)", |
111 | 34 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
112 | 34 | parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString()); |
113 | 34 | } |
114 | | |
115 | 172 | for (const auto& package_tx : package) { Branch (115:41): [True: 172, False: 68]
|
116 | | // Skip same tx. |
117 | 172 | if (&(*package_tx) == &(*ptx)) continue; Branch (117:21): [True: 86, False: 86]
|
118 | | |
119 | 104 | for (auto& input : package_tx->vin) { Branch (119:34): [True: 104, False: 68]
|
120 | | // Fail if we find another tx with the same parent. We don't check whether the |
121 | | // sibling is to-be-replaced (done in SingleTRUCChecks) because these transactions |
122 | | // are within the same package. |
123 | 104 | if (input.prevout.hash == parent_info.m_txid) { Branch (123:25): [True: 0, False: 104]
|
124 | 0 | return strprintf("tx %s (wtxid=%s) would exceed descendant count limit", |
125 | 0 | parent_info.m_txid.ToString(), |
126 | 0 | parent_info.m_wtxid.ToString()); |
127 | 0 | } |
128 | | |
129 | | // This tx can't have both a parent and an in-package child. |
130 | 104 | if (input.prevout.hash == ptx->GetHash()) { Branch (130:25): [True: 18, False: 86]
|
131 | 18 | return strprintf("tx %s (wtxid=%s) would have too many ancestors", |
132 | 18 | package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString()); |
133 | 18 | } |
134 | 104 | } |
135 | 86 | } |
136 | | |
137 | 68 | if (parent_info.m_has_mempool_descendant) { Branch (137:17): [True: 0, False: 68]
|
138 | 0 | return strprintf("tx %s (wtxid=%s) would exceed descendant count limit", |
139 | 0 | parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString()); |
140 | 0 | } |
141 | 68 | } |
142 | 2.19k | } else { |
143 | | // Non-TRUC transactions cannot have TRUC parents. |
144 | 2.19k | for (auto it : mempool_ancestors) { Branch (144:22): [True: 140, False: 2.19k]
|
145 | 140 | if (it->GetTx().version == TRUC_VERSION) { Branch (145:17): [True: 0, False: 140]
|
146 | 0 | return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)", |
147 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
148 | 0 | it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString()); |
149 | 0 | } |
150 | 140 | } |
151 | 2.19k | for (const auto& index: in_package_parents) { Branch (151:31): [True: 1.11k, False: 2.12k]
|
152 | 1.11k | if (package.at(index)->version == TRUC_VERSION) { Branch (152:17): [True: 70, False: 1.04k]
|
153 | 70 | return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)", |
154 | 70 | ptx->GetHash().ToString(), |
155 | 70 | ptx->GetWitnessHash().ToString(), |
156 | 70 | package.at(index)->GetHash().ToString(), |
157 | 70 | package.at(index)->GetWitnessHash().ToString()); |
158 | 70 | } |
159 | 1.11k | } |
160 | 2.19k | } |
161 | 2.33k | return std::nullopt; |
162 | 2.46k | } |
163 | | |
164 | | std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx, |
165 | | const CTxMemPool::setEntries& mempool_ancestors, |
166 | | const std::set<Txid>& direct_conflicts, |
167 | | int64_t vsize) |
168 | 57.2k | { |
169 | | // Check TRUC and non-TRUC inheritance. |
170 | 313k | for (const auto& entry : mempool_ancestors) { Branch (170:28): [True: 313k, False: 56.8k]
|
171 | 313k | if (ptx->version != TRUC_VERSION && entry->GetTx().version == TRUC_VERSION) { Branch (171:13): [True: 313k, False: 378]
Branch (171:45): [True: 191, False: 313k]
|
172 | 191 | return std::make_pair(strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)", |
173 | 191 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
174 | 191 | entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), |
175 | 191 | nullptr); |
176 | 313k | } else if (ptx->version == TRUC_VERSION && entry->GetTx().version != TRUC_VERSION) { Branch (176:20): [True: 378, False: 313k]
Branch (176:52): [True: 172, False: 206]
|
177 | 172 | return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)", |
178 | 172 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
179 | 172 | entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), |
180 | 172 | nullptr); |
181 | 172 | } |
182 | 313k | } |
183 | | |
184 | | // This function is specialized for these limits, and must be reimplemented if they ever change. |
185 | 56.8k | static_assert(TRUC_ANCESTOR_LIMIT == 2); |
186 | 56.8k | static_assert(TRUC_DESCENDANT_LIMIT == 2); |
187 | | |
188 | | // The rest of the rules only apply to transactions with version=3. |
189 | 56.8k | if (ptx->version != TRUC_VERSION) return std::nullopt; Branch (189:9): [True: 55.7k, False: 1.09k]
|
190 | | |
191 | 1.09k | if (vsize > TRUC_MAX_VSIZE) { Branch (191:9): [True: 166, False: 931]
|
192 | 166 | return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
193 | 166 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_MAX_VSIZE), |
194 | 166 | nullptr); |
195 | 166 | } |
196 | | |
197 | | // Check that TRUC_ANCESTOR_LIMIT would not be violated. |
198 | 931 | if (mempool_ancestors.size() + 1 > TRUC_ANCESTOR_LIMIT) { Branch (198:9): [True: 51, False: 880]
|
199 | 51 | return std::make_pair(strprintf("tx %s (wtxid=%s) would have too many ancestors", |
200 | 51 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()), |
201 | 51 | nullptr); |
202 | 51 | } |
203 | | |
204 | | // Remaining checks only pertain to transactions with unconfirmed ancestors. |
205 | 880 | if (mempool_ancestors.size() > 0) { Branch (205:9): [True: 102, False: 778]
|
206 | | // If this transaction spends TRUC parents, it cannot be too large. |
207 | 102 | if (vsize > TRUC_CHILD_MAX_VSIZE) { Branch (207:13): [True: 7, False: 95]
|
208 | 7 | return std::make_pair(strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
209 | 7 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_CHILD_MAX_VSIZE), |
210 | 7 | nullptr); |
211 | 7 | } |
212 | | |
213 | | // Check the descendant counts of in-mempool ancestors. |
214 | 95 | const auto& parent_entry = *mempool_ancestors.begin(); |
215 | | // If there are any ancestors, this is the only child allowed. The parent cannot have any |
216 | | // other descendants. We handle the possibility of multiple children as that case is |
217 | | // possible through a reorg. |
218 | 95 | const auto& children = parent_entry->GetMemPoolChildrenConst(); |
219 | | // Don't double-count a transaction that is going to be replaced. This logic assumes that |
220 | | // any descendant of the TRUC transaction is a direct child, which makes sense because a |
221 | | // TRUC transaction can only have 1 descendant. |
222 | 95 | const bool child_will_be_replaced = !children.empty() && Branch (222:45): [True: 58, False: 37]
|
223 | 95 | std::any_of(children.cbegin(), children.cend(), Branch (223:13): [True: 35, False: 23]
|
224 | 58 | [&direct_conflicts](const CTxMemPoolEntry& child){return direct_conflicts.count(child.GetTx().GetHash()) > 0;}); |
225 | 95 | if (parent_entry->GetCountWithDescendants() + 1 > TRUC_DESCENDANT_LIMIT && !child_will_be_replaced) { Branch (225:13): [True: 58, False: 37]
Branch (225:84): [True: 23, False: 35]
|
226 | | // Allow sibling eviction for TRUC transaction: if another child already exists, even if |
227 | | // we don't conflict inputs with it, consider evicting it under RBF rules. We rely on TRUC rules |
228 | | // only permitting 1 descendant, as otherwise we would need to have logic for deciding |
229 | | // which descendant to evict. Skip if this isn't true, e.g. if the transaction has |
230 | | // multiple children or the sibling also has descendants due to a reorg. |
231 | 23 | const bool consider_sibling_eviction{parent_entry->GetCountWithDescendants() == 2 && Branch (231:50): [True: 23, False: 0]
|
232 | 23 | children.begin()->get().GetCountWithAncestors() == 2}; Branch (232:17): [True: 23, False: 0]
|
233 | | |
234 | | // Return the sibling if its eviction can be considered. Provide the "descendant count |
235 | | // limit" string either way, as the caller may decide not to do sibling eviction. |
236 | 23 | return std::make_pair(strprintf("tx %u (wtxid=%s) would exceed descendant count limit", |
237 | 23 | parent_entry->GetSharedTx()->GetHash().ToString(), |
238 | 23 | parent_entry->GetSharedTx()->GetWitnessHash().ToString()), |
239 | 23 | consider_sibling_eviction ? children.begin()->get().GetSharedTx() : nullptr); Branch (239:35): [True: 23, False: 0]
|
240 | 23 | } |
241 | 95 | } |
242 | 850 | return std::nullopt; |
243 | 880 | } |