/Users/eugenesiegel/btc/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)) {| Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 72 | 0 |             return strprintf("version=3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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)",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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)",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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)",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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 | 638k | { | 
| 169 |  |     // Check TRUC and non-TRUC inheritance. | 
| 170 | 1.20M |     for (const auto& entry : mempool_ancestors) { | 
| 171 | 1.20M |         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)",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 173 | 0 |                              ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), | 
| 174 | 0 |                              entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), | 
| 175 | 0 |                 nullptr); | 
| 176 | 1.20M |         } else if (ptx->version == TRUC_VERSION && entry->GetTx().version != TRUC_VERSION0) { | 
| 177 | 0 |             return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 178 | 0 |                              ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), | 
| 179 | 0 |                              entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), | 
| 180 | 0 |                 nullptr); | 
| 181 | 0 |         } | 
| 182 | 1.20M |     } | 
| 183 |  |  | 
| 184 |  |     // This function is specialized for these limits, and must be reimplemented if they ever change. | 
| 185 | 638k |     static_assert(TRUC_ANCESTOR_LIMIT == 2); | 
| 186 | 638k |     static_assert(TRUC_DESCENDANT_LIMIT == 2); | 
| 187 |  |  | 
| 188 |  |     // The rest of the rules only apply to transactions with version=3. | 
| 189 | 638k |     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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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",| Line | Count | Source |  | 1172 | 0 | #define strprintf tfm::format | 
 | 
| 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 | } |