/root/bitcoin/src/policy/truc_policy.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | { |
21 | 0 | std::vector<size_t> in_package_parents; |
22 | |
|
23 | 0 | std::set<Txid> possible_parents; |
24 | 0 | for (auto &input : ptx->vin) { |
25 | 0 | possible_parents.insert(input.prevout.hash); |
26 | 0 | } |
27 | |
|
28 | 0 | for (size_t i{0}; i < package.size(); ++i) { |
29 | 0 | 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 | 0 | if (&(*tx) == &(*ptx)) break; |
33 | 0 | if (possible_parents.count(tx->GetHash())) { |
34 | 0 | in_package_parents.push_back(i); |
35 | 0 | } |
36 | 0 | } |
37 | 0 | return in_package_parents; |
38 | 0 | } |
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 | 0 | m_txid{txid}, m_wtxid{wtxid}, m_version{version}, |
54 | 0 | m_has_mempool_descendant{has_mempool_descendant} |
55 | 0 | {} |
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 | 0 | { |
62 | | // This function is specialized for these limits, and must be reimplemented if they ever change. |
63 | 0 | static_assert(TRUC_ANCESTOR_LIMIT == 2); |
64 | 0 | static_assert(TRUC_DESCENDANT_LIMIT == 2); |
65 | |
|
66 | 0 | const auto in_package_parents{FindInPackageParents(package, ptx)}; |
67 | | |
68 | | // Now we have all ancestors, so we can start checking TRUC rules. |
69 | 0 | if (ptx->version == TRUC_VERSION) { |
70 | | // SingleTRUCChecks should have checked this already. |
71 | 0 | if (!Assume(vsize <= TRUC_MAX_VSIZE)) { |
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 | 0 | if (mempool_ancestors.size() + in_package_parents.size() + 1 > TRUC_ANCESTOR_LIMIT) { |
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 | 0 | const bool has_parent{mempool_ancestors.size() + in_package_parents.size() > 0}; |
82 | 0 | if (has_parent) { |
83 | | // A TRUC child cannot be too large. |
84 | 0 | if (vsize > TRUC_CHILD_MAX_VSIZE) { |
85 | 0 | return strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
86 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
87 | 0 | vsize, TRUC_CHILD_MAX_VSIZE); |
88 | 0 | } |
89 | | |
90 | | // Exactly 1 parent exists, either in mempool or package. Find it. |
91 | 0 | const auto parent_info = [&] { |
92 | 0 | if (mempool_ancestors.size() > 0) { |
93 | 0 | auto& mempool_parent = *mempool_ancestors.begin(); |
94 | 0 | return ParentInfo{mempool_parent->GetTx().GetHash(), |
95 | 0 | mempool_parent->GetTx().GetWitnessHash(), |
96 | 0 | mempool_parent->GetTx().version, |
97 | 0 | /*has_mempool_descendant=*/mempool_parent->GetCountWithDescendants() > 1}; |
98 | 0 | } else { |
99 | 0 | auto& parent_index = in_package_parents.front(); |
100 | 0 | auto& package_parent = package.at(parent_index); |
101 | 0 | return ParentInfo{package_parent->GetHash(), |
102 | 0 | package_parent->GetWitnessHash(), |
103 | 0 | package_parent->version, |
104 | 0 | /*has_mempool_descendant=*/false}; |
105 | 0 | } |
106 | 0 | }(); |
107 | | |
108 | | // If there is a parent, it must have the right version. |
109 | 0 | if (parent_info.m_version != TRUC_VERSION) { |
110 | 0 | return strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)", |
111 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
112 | 0 | parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString()); |
113 | 0 | } |
114 | | |
115 | 0 | for (const auto& package_tx : package) { |
116 | | // Skip same tx. |
117 | 0 | if (&(*package_tx) == &(*ptx)) continue; |
118 | | |
119 | 0 | for (auto& input : package_tx->vin) { |
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 | 0 | if (input.prevout.hash == parent_info.m_txid) { |
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 | 0 | if (input.prevout.hash == ptx->GetHash()) { |
131 | 0 | return strprintf("tx %s (wtxid=%s) would have too many ancestors", |
132 | 0 | package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString()); |
133 | 0 | } |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | 0 | if (parent_info.m_has_mempool_descendant) { |
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 | 0 | } |
142 | 0 | } else { |
143 | | // Non-TRUC transactions cannot have TRUC parents. |
144 | 0 | for (auto it : mempool_ancestors) { |
145 | 0 | if (it->GetTx().version == TRUC_VERSION) { |
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 | 0 | } |
151 | 0 | for (const auto& index: in_package_parents) { |
152 | 0 | if (package.at(index)->version == TRUC_VERSION) { |
153 | 0 | return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)", |
154 | 0 | ptx->GetHash().ToString(), |
155 | 0 | ptx->GetWitnessHash().ToString(), |
156 | 0 | package.at(index)->GetHash().ToString(), |
157 | 0 | package.at(index)->GetWitnessHash().ToString()); |
158 | 0 | } |
159 | 0 | } |
160 | 0 | } |
161 | 0 | return std::nullopt; |
162 | 0 | } |
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 | 0 | { |
169 | | // Check TRUC and non-TRUC inheritance. |
170 | 0 | for (const auto& entry : mempool_ancestors) { |
171 | 0 | if (ptx->version != TRUC_VERSION && entry->GetTx().version == TRUC_VERSION) { |
172 | 0 | return std::make_pair(strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)", |
173 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
174 | 0 | entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), |
175 | 0 | nullptr); |
176 | 0 | } else if (ptx->version == TRUC_VERSION && entry->GetTx().version != TRUC_VERSION) { |
177 | 0 | return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)", |
178 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), |
179 | 0 | entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), |
180 | 0 | nullptr); |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | // This function is specialized for these limits, and must be reimplemented if they ever change. |
185 | 0 | static_assert(TRUC_ANCESTOR_LIMIT == 2); |
186 | 0 | static_assert(TRUC_DESCENDANT_LIMIT == 2); |
187 | | |
188 | | // The rest of the rules only apply to transactions with version=3. |
189 | 0 | if (ptx->version != TRUC_VERSION) return std::nullopt; |
190 | | |
191 | 0 | if (vsize > TRUC_MAX_VSIZE) { |
192 | 0 | return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
193 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_MAX_VSIZE), |
194 | 0 | nullptr); |
195 | 0 | } |
196 | | |
197 | | // Check that TRUC_ANCESTOR_LIMIT would not be violated. |
198 | 0 | if (mempool_ancestors.size() + 1 > TRUC_ANCESTOR_LIMIT) { |
199 | 0 | return std::make_pair(strprintf("tx %s (wtxid=%s) would have too many ancestors", |
200 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()), |
201 | 0 | nullptr); |
202 | 0 | } |
203 | | |
204 | | // Remaining checks only pertain to transactions with unconfirmed ancestors. |
205 | 0 | if (mempool_ancestors.size() > 0) { |
206 | | // If this transaction spends TRUC parents, it cannot be too large. |
207 | 0 | if (vsize > TRUC_CHILD_MAX_VSIZE) { |
208 | 0 | return std::make_pair(strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes", |
209 | 0 | ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_CHILD_MAX_VSIZE), |
210 | 0 | nullptr); |
211 | 0 | } |
212 | | |
213 | | // Check the descendant counts of in-mempool ancestors. |
214 | 0 | 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 | 0 | 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 | 0 | const bool child_will_be_replaced = !children.empty() && |
223 | 0 | std::any_of(children.cbegin(), children.cend(), |
224 | 0 | [&direct_conflicts](const CTxMemPoolEntry& child){return direct_conflicts.count(child.GetTx().GetHash()) > 0;}); |
225 | 0 | if (parent_entry->GetCountWithDescendants() + 1 > TRUC_DESCENDANT_LIMIT && !child_will_be_replaced) { |
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 | 0 | const bool consider_sibling_eviction{parent_entry->GetCountWithDescendants() == 2 && |
232 | 0 | children.begin()->get().GetCountWithAncestors() == 2}; |
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 | 0 | return std::make_pair(strprintf("tx %u (wtxid=%s) would exceed descendant count limit", |
237 | 0 | parent_entry->GetSharedTx()->GetHash().ToString(), |
238 | 0 | parent_entry->GetSharedTx()->GetWitnessHash().ToString()), |
239 | 0 | consider_sibling_eviction ? children.begin()->get().GetSharedTx() : nullptr); |
240 | 0 | } |
241 | 0 | } |
242 | 0 | return std::nullopt; |
243 | 0 | } |