txmempool.cpp
1 // Copyright (c) 2022-present 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 <test/util/txmempool.h> 6 7 #include <chainparams.h> 8 #include <node/context.h> 9 #include <node/mempool_args.h> 10 #include <policy/rbf.h> 11 #include <policy/truc_policy.h> 12 #include <txmempool.h> 13 #include <test/util/transaction_utils.h> 14 #include <util/check.h> 15 #include <util/time.h> 16 #include <util/translation.h> 17 #include <validation.h> 18 19 using node::NodeContext; 20 21 CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node) 22 { 23 CTxMemPool::Options mempool_opts{ 24 // Default to always checking mempool regardless of 25 // chainparams.DefaultConsistencyChecks for tests 26 .check_ratio = 1, 27 .signals = node.validation_signals.get(), 28 }; 29 const auto result{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)}; 30 Assert(result); 31 return mempool_opts; 32 } 33 34 CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const 35 { 36 return FromTx(MakeTransactionRef(tx)); 37 } 38 39 CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const 40 { 41 return CTxMemPoolEntry{tx, nFee, TicksSinceEpoch<std::chrono::seconds>(time), nHeight, m_sequence, spendsCoinbase, sigOpCost, lp}; 42 } 43 44 std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns, 45 const PackageMempoolAcceptResult& result, 46 bool expect_valid, 47 const CTxMemPool* mempool) 48 { 49 if (expect_valid) { 50 if (result.m_state.IsInvalid()) { 51 return strprintf("Package validation unexpectedly failed: %s", result.m_state.ToString()); 52 } 53 } else { 54 if (result.m_state.IsValid()) { 55 return strprintf("Package validation unexpectedly succeeded. %s", result.m_state.ToString()); 56 } 57 } 58 if (result.m_state.GetResult() != PackageValidationResult::PCKG_POLICY && txns.size() != result.m_tx_results.size()) { 59 return strprintf("txns size %u does not match tx results size %u", txns.size(), result.m_tx_results.size()); 60 } 61 for (const auto& tx : txns) { 62 const auto& wtxid = tx->GetWitnessHash(); 63 if (!result.m_tx_results.contains(wtxid)) { 64 return strprintf("result not found for tx %s", wtxid.ToString()); 65 } 66 67 const auto& atmp_result = result.m_tx_results.at(wtxid); 68 const bool valid{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID}; 69 if (expect_valid && atmp_result.m_state.IsInvalid()) { 70 return strprintf("tx %s unexpectedly failed: %s", wtxid.ToString(), atmp_result.m_state.ToString()); 71 } 72 73 // Each subpackage is allowed MAX_REPLACEMENT_CANDIDATES replacements (only checking individually here) 74 if (atmp_result.m_replaced_transactions.size() > MAX_REPLACEMENT_CANDIDATES) { 75 return strprintf("tx %s result replaced too many transactions", 76 wtxid.ToString()); 77 } 78 79 // Replacements can't happen for subpackages larger than 2 80 if (!atmp_result.m_replaced_transactions.empty() && 81 atmp_result.m_wtxids_fee_calculations.has_value() && atmp_result.m_wtxids_fee_calculations.value().size() > 2) { 82 return strprintf("tx %s was part of a too-large package RBF subpackage", 83 wtxid.ToString()); 84 } 85 86 if (!atmp_result.m_replaced_transactions.empty() && mempool) { 87 LOCK(mempool->cs); 88 // If replacements occurred and it used 2 transactions, this is a package RBF and should result in a cluster of size 2 89 if (atmp_result.m_wtxids_fee_calculations.has_value() && atmp_result.m_wtxids_fee_calculations.value().size() == 2) { 90 const auto cluster = mempool->GatherClusters({tx->GetHash()}); 91 if (cluster.size() != 2) return strprintf("tx %s has too many ancestors or descendants for a package rbf", wtxid.ToString()); 92 } 93 } 94 95 // m_vsize and m_base_fees should exist iff the result was VALID or MEMPOOL_ENTRY 96 const bool mempool_entry{atmp_result.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY}; 97 if (atmp_result.m_base_fees.has_value() != (valid || mempool_entry)) { 98 return strprintf("tx %s result should %shave m_base_fees", wtxid.ToString(), valid || mempool_entry ? "" : "not "); 99 } 100 if (atmp_result.m_vsize.has_value() != (valid || mempool_entry)) { 101 return strprintf("tx %s result should %shave m_vsize", wtxid.ToString(), valid || mempool_entry ? "" : "not "); 102 } 103 104 // m_other_wtxid should exist iff the result was DIFFERENT_WITNESS 105 const bool diff_witness{atmp_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS}; 106 if (atmp_result.m_other_wtxid.has_value() != diff_witness) { 107 return strprintf("tx %s result should %shave m_other_wtxid", wtxid.ToString(), diff_witness ? "" : "not "); 108 } 109 110 // m_effective_feerate and m_wtxids_fee_calculations should exist iff the result was valid 111 // or if the failure was TX_RECONSIDERABLE 112 const bool valid_or_reconsiderable{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID || 113 atmp_result.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE}; 114 if (atmp_result.m_effective_feerate.has_value() != valid_or_reconsiderable) { 115 return strprintf("tx %s result should %shave m_effective_feerate", 116 wtxid.ToString(), valid ? "" : "not "); 117 } 118 if (atmp_result.m_wtxids_fee_calculations.has_value() != valid_or_reconsiderable) { 119 return strprintf("tx %s result should %shave m_effective_feerate", 120 wtxid.ToString(), valid ? "" : "not "); 121 } 122 123 if (mempool) { 124 // The tx by txid should be in the mempool iff the result was not INVALID. 125 const bool txid_in_mempool{atmp_result.m_result_type != MempoolAcceptResult::ResultType::INVALID}; 126 if (mempool->exists(tx->GetHash()) != txid_in_mempool) { 127 return strprintf("tx %s should %sbe in mempool", wtxid.ToString(), txid_in_mempool ? "" : "not "); 128 } 129 // Additionally, if the result was DIFFERENT_WITNESS, we shouldn't be able to find the tx in mempool by wtxid. 130 if (tx->HasWitness() && atmp_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) { 131 if (mempool->exists(wtxid)) { 132 return strprintf("wtxid %s should not be in mempool", wtxid.ToString()); 133 } 134 } 135 for (const auto& tx_ref : atmp_result.m_replaced_transactions) { 136 if (mempool->exists(tx_ref->GetHash())) { 137 return strprintf("tx %s should not be in mempool as it was replaced", tx_ref->GetWitnessHash().ToString()); 138 } 139 } 140 } 141 } 142 return std::nullopt; 143 } 144 145 void CheckMempoolEphemeralInvariants(const CTxMemPool& tx_pool) 146 { 147 LOCK(tx_pool.cs); 148 for (const auto& tx_info : tx_pool.infoAll()) { 149 const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash())); 150 151 std::vector<uint32_t> dust_indexes = GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate); 152 153 Assert(dust_indexes.size() < 2); 154 155 if (dust_indexes.empty()) continue; 156 157 // Transaction must have no base fee 158 Assert(entry.GetFee() == 0 && entry.GetModifiedFee() == 0); 159 160 // Transaction has single dust; make sure it's swept or will not be mined 161 const auto& children = tx_pool.GetChildren(entry); 162 163 // Multiple children should never happen as non-dust-spending child 164 // can get mined as package 165 Assert(children.size() < 2); 166 167 if (children.empty()) { 168 // No children and no fees; modified fees aside won't get mined so it's fine 169 // Happens naturally if child spend is RBF cycled away. 170 continue; 171 } 172 173 // Only-child should be spending the dust 174 const auto& only_child = children.begin()->get().GetTx(); 175 COutPoint dust_outpoint{tx_info.tx->GetHash(), dust_indexes[0]}; 176 Assert(std::any_of(only_child.vin.begin(), only_child.vin.end(), [&dust_outpoint](const CTxIn& txin) { 177 return txin.prevout == dust_outpoint; 178 })); 179 } 180 } 181 182 void CheckMempoolTRUCInvariants(const CTxMemPool& tx_pool) 183 { 184 LOCK(tx_pool.cs); 185 for (const auto& tx_info : tx_pool.infoAll()) { 186 const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash())); 187 auto [desc_count, desc_size, desc_fees] = tx_pool.CalculateDescendantData(entry); 188 auto [anc_count, anc_size, anc_fees] = tx_pool.CalculateAncestorData(entry); 189 190 if (tx_info.tx->version == TRUC_VERSION) { 191 // Check that special maximum virtual size is respected 192 Assert(entry.GetTxSize() <= TRUC_MAX_VSIZE); 193 194 // Check that special TRUC ancestor/descendant limits and rules are always respected 195 Assert(desc_count <= TRUC_DESCENDANT_LIMIT); 196 Assert(anc_count <= TRUC_ANCESTOR_LIMIT); 197 Assert(desc_size <= TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE); 198 Assert(anc_size <= TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE); 199 // If this transaction has at least 1 ancestor, it's a "child" and has restricted weight. 200 if (anc_count > 1) { 201 Assert(entry.GetTxSize() <= TRUC_CHILD_MAX_VSIZE); 202 // All TRUC transactions must only have TRUC unconfirmed parents. 203 const auto& parents = tx_pool.GetParents(entry); 204 Assert(parents.begin()->get().GetSharedTx()->version == TRUC_VERSION); 205 } 206 } else if (anc_count > 1) { 207 // All non-TRUC transactions must only have non-TRUC unconfirmed parents. 208 for (const auto& parent : tx_pool.GetParents(entry)) { 209 Assert(parent.get().GetSharedTx()->version != TRUC_VERSION); 210 } 211 } 212 } 213 } 214 215 void TryAddToMempool(CTxMemPool& tx_pool, const CTxMemPoolEntry& entry) 216 { 217 LOCK2(cs_main, tx_pool.cs); 218 auto changeset = tx_pool.GetChangeSet(); 219 changeset->StageAddition(entry.GetSharedTx(), entry.GetFee(), 220 entry.GetTime().count(), entry.GetHeight(), entry.GetSequence(), 221 entry.GetSpendsCoinbase(), entry.GetSigOpCost(), entry.GetLockPoints()); 222 if (changeset->CheckMemPoolPolicyLimits()) changeset->Apply(); 223 } 224 225 void MockMempoolMinFee(const CFeeRate& target_feerate, CTxMemPool& mempool) 226 { 227 LOCK2(cs_main, mempool.cs); 228 // Transactions in the mempool will affect the new minimum feerate. 229 assert(mempool.size() == 0); 230 // The target feerate cannot be too low... 231 // ...otherwise the transaction's feerate will need to be negative. 232 assert(target_feerate > mempool.m_opts.incremental_relay_feerate); 233 // ...otherwise this is not meaningful. The feerate policy uses the maximum of both feerates. 234 assert(target_feerate > mempool.m_opts.min_relay_feerate); 235 236 // Manually create an invalid transaction. Manually set the fee in the CTxMemPoolEntry to 237 // achieve the exact target feerate. 238 CMutableTransaction mtx{}; 239 mtx.vin.emplace_back(COutPoint{Txid::FromUint256(uint256{123}), 0}); 240 mtx.vout.emplace_back(1 * COIN, GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE))); 241 // Set a large size so that the fee evaluated at target_feerate (which is usually in sats/kvB) is an integer. 242 // Otherwise, GetMinFee() may end up slightly different from target_feerate. 243 BulkTransaction(mtx, 4000); 244 const auto tx{MakeTransactionRef(mtx)}; 245 LockPoints lp; 246 // The new mempool min feerate is equal to the removed package's feerate + incremental feerate. 247 const auto tx_fee = target_feerate.GetFee(GetVirtualTransactionSize(*tx)) - 248 mempool.m_opts.incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx)); 249 { 250 auto changeset = mempool.GetChangeSet(); 251 changeset->StageAddition(tx, /*fee=*/tx_fee, 252 /*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0, 253 /*spends_coinbase=*/true, /*sigops_cost=*/1, lp); 254 changeset->Apply(); 255 } 256 mempool.TrimToSize(0); 257 assert(mempool.GetMinFee() == target_feerate); 258 }