txdownload_tests.cpp
1 // Copyright (c) 2011-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 <addresstype.h> 6 #include <consensus/validation.h> 7 #include <net_processing.h> 8 #include <node/txdownloadman_impl.h> 9 #include <primitives/transaction.h> 10 #include <script/script.h> 11 #include <test/util/common.h> 12 #include <test/util/random.h> 13 #include <test/util/setup_common.h> 14 #include <validation.h> 15 16 #include <array> 17 18 #include <boost/test/unit_test.hpp> 19 20 BOOST_AUTO_TEST_SUITE(txdownload_tests) 21 22 struct Behaviors { 23 bool m_txid_in_rejects; 24 bool m_wtxid_in_rejects; 25 bool m_txid_in_rejects_recon; 26 bool m_wtxid_in_rejects_recon; 27 bool m_keep_for_compact; 28 bool m_ignore_inv_txid; 29 bool m_ignore_inv_wtxid; 30 31 // Constructor. We are passing and casting ints because they are more readable in a table (see expected_behaviors). 32 Behaviors(bool txid_rejects, bool wtxid_rejects, bool txid_recon, bool wtxid_recon, bool keep, bool txid_inv, bool wtxid_inv) : 33 m_txid_in_rejects(txid_rejects), 34 m_wtxid_in_rejects(wtxid_rejects), 35 m_txid_in_rejects_recon(txid_recon), 36 m_wtxid_in_rejects_recon(wtxid_recon), 37 m_keep_for_compact(keep), 38 m_ignore_inv_txid(txid_inv), 39 m_ignore_inv_wtxid(wtxid_inv) 40 {} 41 42 void CheckEqual(const Behaviors& other, bool segwit) 43 { 44 BOOST_CHECK_EQUAL(other.m_wtxid_in_rejects, m_wtxid_in_rejects); 45 BOOST_CHECK_EQUAL(other.m_wtxid_in_rejects_recon, m_wtxid_in_rejects_recon); 46 BOOST_CHECK_EQUAL(other.m_keep_for_compact, m_keep_for_compact); 47 BOOST_CHECK_EQUAL(other.m_ignore_inv_wtxid, m_ignore_inv_wtxid); 48 49 // false negatives for nonsegwit transactions, since txid == wtxid. 50 if (segwit) { 51 BOOST_CHECK_EQUAL(other.m_txid_in_rejects, m_txid_in_rejects); 52 BOOST_CHECK_EQUAL(other.m_txid_in_rejects_recon, m_txid_in_rejects_recon); 53 BOOST_CHECK_EQUAL(other.m_ignore_inv_txid, m_ignore_inv_txid); 54 } 55 } 56 }; 57 58 // Map from failure reason to expected behavior for a segwit tx that fails 59 // Txid and Wtxid are assumed to be different here. For a nonsegwit transaction, use the wtxid results. 60 static std::map<TxValidationResult, Behaviors> expected_behaviors{ 61 {TxValidationResult::TX_CONSENSUS, {/*txid_rejects*/0,/*wtxid_rejects*/1,/*txid_recon*/0,/*wtxid_recon*/0,/*keep*/1,/*txid_inv*/0,/*wtxid_inv*/1}}, 62 {TxValidationResult::TX_INPUTS_NOT_STANDARD, { 1, 1, 0, 0, 1, 1, 1}}, 63 {TxValidationResult::TX_NOT_STANDARD, { 0, 1, 0, 0, 1, 0, 1}}, 64 {TxValidationResult::TX_MISSING_INPUTS, { 0, 0, 0, 0, 1, 0, 1}}, 65 {TxValidationResult::TX_PREMATURE_SPEND, { 0, 1, 0, 0, 1, 0, 1}}, 66 {TxValidationResult::TX_WITNESS_MUTATED, { 0, 1, 0, 0, 1, 0, 1}}, 67 {TxValidationResult::TX_WITNESS_STRIPPED, { 0, 0, 0, 0, 0, 0, 0}}, 68 {TxValidationResult::TX_CONFLICT, { 0, 1, 0, 0, 1, 0, 1}}, 69 {TxValidationResult::TX_MEMPOOL_POLICY, { 0, 1, 0, 0, 1, 0, 1}}, 70 {TxValidationResult::TX_NO_MEMPOOL, { 0, 1, 0, 0, 1, 0, 1}}, 71 {TxValidationResult::TX_RECONSIDERABLE, { 0, 0, 0, 1, 1, 0, 1}}, 72 {TxValidationResult::TX_UNKNOWN, { 0, 1, 0, 0, 1, 0, 1}}, 73 }; 74 75 static bool CheckOrphanBehavior(node::TxDownloadManagerImpl& txdownload_impl, const CTransactionRef& tx, const node::RejectedTxTodo& ret, std::string& err_msg, 76 bool expect_orphan, bool expect_keep, unsigned int expected_parents) 77 { 78 // Missing inputs can never result in a PackageToValidate. 79 if (ret.m_package_to_validate.has_value()) { 80 err_msg = strprintf("returned a PackageToValidate on missing inputs"); 81 return false; 82 } 83 84 if (expect_orphan != txdownload_impl.m_orphanage->HaveTx(tx->GetWitnessHash())) { 85 err_msg = strprintf("unexpectedly %s tx in orphanage", expect_orphan ? "did not find" : "found"); 86 return false; 87 } 88 if (expect_keep != ret.m_should_add_extra_compact_tx) { 89 err_msg = strprintf("unexpectedly returned %s add to vExtraTxnForCompact", expect_keep ? "should not" : "should"); 90 return false; 91 } 92 if (expected_parents != ret.m_unique_parents.size()) { 93 err_msg = strprintf("expected %u unique_parents, got %u", expected_parents, ret.m_unique_parents.size()); 94 return false; 95 } 96 return true; 97 } 98 99 static CTransactionRef CreatePlaceholderTx(bool segwit) 100 { 101 // Each tx returned from here spends the previous one. 102 static Txid prevout_hash{}; 103 104 CMutableTransaction mtx; 105 mtx.vin.emplace_back(prevout_hash, 0); 106 // This makes txid != wtxid 107 if (segwit) mtx.vin[0].scriptWitness.stack.push_back({1}); 108 mtx.vout.emplace_back(CENT, CScript()); 109 auto ptx = MakeTransactionRef(mtx); 110 prevout_hash = ptx->GetHash(); 111 return ptx; 112 } 113 114 BOOST_FIXTURE_TEST_CASE(tx_rejection_types, TestChain100Setup) 115 { 116 CTxMemPool& pool = *Assert(m_node.mempool); 117 FastRandomContext det_rand{true}; 118 node::TxDownloadOptions DEFAULT_OPTS{pool, det_rand, true}; 119 120 // A new TxDownloadManagerImpl is created for each tx so we can just reuse the same one. 121 TxValidationState state; 122 NodeId nodeid{0}; 123 std::chrono::microseconds now{GetTime()}; 124 node::TxDownloadConnectionInfo connection_info{/*m_preferred=*/false, /*m_relay_permissions=*/false, /*m_wtxid_relay=*/true}; 125 126 for (const auto segwit_parent : {true, false}) { 127 for (const auto segwit_child : {true, false}) { 128 const auto ptx_parent = CreatePlaceholderTx(segwit_parent); 129 const auto ptx_child = CreatePlaceholderTx(segwit_child); 130 const auto& parent_txid = ptx_parent->GetHash(); 131 const auto& parent_wtxid = ptx_parent->GetWitnessHash(); 132 const auto& child_txid = ptx_child->GetHash(); 133 const auto& child_wtxid = ptx_child->GetWitnessHash(); 134 135 for (const auto& [result, expected_behavior] : expected_behaviors) { 136 node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS}; 137 txdownload_impl.ConnectedPeer(nodeid, connection_info); 138 // Parent failure 139 state.Invalid(result, ""); 140 const auto& [keep, unique_txids, package_to_validate] = txdownload_impl.MempoolRejectedTx(ptx_parent, state, nodeid, /*first_time_failure=*/true); 141 142 // No distinction between txid and wtxid caching for nonsegwit transactions, so only test these specific 143 // behaviors for segwit transactions. 144 Behaviors actual_behavior{ 145 /*txid_rejects=*/txdownload_impl.RecentRejectsFilter().contains(parent_txid.ToUint256()), 146 /*wtxid_rejects=*/txdownload_impl.RecentRejectsFilter().contains(parent_wtxid.ToUint256()), 147 /*txid_recon=*/txdownload_impl.RecentRejectsReconsiderableFilter().contains(parent_txid.ToUint256()), 148 /*wtxid_recon=*/txdownload_impl.RecentRejectsReconsiderableFilter().contains(parent_wtxid.ToUint256()), 149 /*keep=*/keep, 150 /*txid_inv=*/txdownload_impl.AddTxAnnouncement(nodeid, parent_txid, now), 151 /*wtxid_inv=*/txdownload_impl.AddTxAnnouncement(nodeid, parent_wtxid, now), 152 }; 153 BOOST_TEST_MESSAGE("Testing behavior for " << result << (segwit_parent ? " segwit " : " nonsegwit")); 154 actual_behavior.CheckEqual(expected_behavior, /*segwit=*/segwit_parent); 155 156 // Later, a child of this transaction fails for missing inputs 157 state.Invalid(TxValidationResult::TX_MISSING_INPUTS, ""); 158 txdownload_impl.MempoolRejectedTx(ptx_child, state, nodeid, /*first_time_failure=*/true); 159 160 // If parent (by txid) was rejected, child is too. 161 const bool parent_txid_rejected{segwit_parent ? expected_behavior.m_txid_in_rejects : expected_behavior.m_wtxid_in_rejects}; 162 BOOST_CHECK_EQUAL(parent_txid_rejected, txdownload_impl.RecentRejectsFilter().contains(child_txid.ToUint256())); 163 BOOST_CHECK_EQUAL(parent_txid_rejected, txdownload_impl.RecentRejectsFilter().contains(child_wtxid.ToUint256())); 164 165 // Unless rejected, the child should be in orphanage. 166 BOOST_CHECK_EQUAL(!parent_txid_rejected, txdownload_impl.m_orphanage->HaveTx(ptx_child->GetWitnessHash())); 167 } 168 } 169 } 170 } 171 172 BOOST_FIXTURE_TEST_CASE(handle_missing_inputs, TestChain100Setup) 173 { 174 CTxMemPool& pool = *Assert(m_node.mempool); 175 FastRandomContext det_rand{true}; 176 node::TxDownloadOptions DEFAULT_OPTS{pool, det_rand, true}; 177 NodeId nodeid{1}; 178 node::TxDownloadConnectionInfo DEFAULT_CONN{/*m_preferred=*/false, /*m_relay_permissions=*/false, /*m_wtxid_relay=*/true}; 179 180 // We need mature coinbases 181 mineBlocks(20); 182 183 // Transactions with missing inputs are treated differently depending on how much we know about 184 // their parents. 185 CKey wallet_key = GenerateRandomKey(); 186 CScript destination = GetScriptForDestination(PKHash(wallet_key.GetPubKey())); 187 // Amount for spending coinbase in a 1-in-1-out tx, at depth n, each time deducting 1000 from the amount as fees. 188 CAmount amount_depth_1{50 * COIN - 1000}; 189 CAmount amount_depth_2{amount_depth_1 - 1000}; 190 // Amount for spending coinbase in a 1-in-2-out tx, deducting 1000 in fees 191 CAmount amount_split_half{25 * COIN - 500}; 192 int test_chain_height{100}; 193 194 TxValidationState state_orphan; 195 state_orphan.Invalid(TxValidationResult::TX_MISSING_INPUTS, ""); 196 197 // Transactions are not all submitted to mempool. Conserve the number of m_coinbase_txns we 198 // consume, and only increment this index number when we would conflict with an existing 199 // mempool transaction. 200 size_t coinbase_idx{0}; 201 202 for (int decisions = 0; decisions < (1 << 4); ++decisions) { 203 auto mtx_single_parent = CreateValidMempoolTransaction(m_coinbase_txns[coinbase_idx], /*input_vout=*/0, test_chain_height, coinbaseKey, destination, amount_depth_1, /*submit=*/false); 204 auto single_parent = MakeTransactionRef(mtx_single_parent); 205 206 auto mtx_orphan = CreateValidMempoolTransaction(single_parent, /*input_vout=*/0, test_chain_height, wallet_key, destination, amount_depth_2, /*submit=*/false); 207 auto orphan = MakeTransactionRef(mtx_orphan); 208 209 node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS}; 210 txdownload_impl.ConnectedPeer(nodeid, DEFAULT_CONN); 211 212 // Each bit of decisions tells us whether the parent is in a particular cache. 213 // It is definitely possible for a transaction to be in multiple caches. For example, it 214 // may have both a low feerate and found to violate some mempool policy when validated 215 // in a 1p1c. 216 const bool parent_recent_rej(decisions & 1); 217 const bool parent_recent_rej_recon((decisions >> 1) & 1); 218 const bool parent_recent_conf((decisions >> 2) & 1); 219 const bool parent_in_mempool((decisions >> 3) & 1); 220 221 if (parent_recent_rej) txdownload_impl.RecentRejectsFilter().insert(single_parent->GetHash().ToUint256()); 222 if (parent_recent_rej_recon) txdownload_impl.RecentRejectsReconsiderableFilter().insert(single_parent->GetHash().ToUint256()); 223 if (parent_recent_conf) txdownload_impl.RecentConfirmedTransactionsFilter().insert(single_parent->GetHash().ToUint256()); 224 if (parent_in_mempool) { 225 const auto mempool_result = WITH_LOCK(::cs_main, return m_node.chainman->ProcessTransaction(single_parent)); 226 BOOST_CHECK(mempool_result.m_result_type == MempoolAcceptResult::ResultType::VALID); 227 coinbase_idx += 1; 228 assert(coinbase_idx < m_coinbase_txns.size()); 229 } 230 231 // Whether or not the transaction is added as an orphan depends solely on whether or not 232 // it's in RecentRejectsFilter. Specifically, the parent is allowed to be in 233 // RecentRejectsReconsiderableFilter, but it cannot be in RecentRejectsFilter. 234 const bool expect_keep_orphan = !parent_recent_rej; 235 const unsigned int expected_parents = parent_recent_rej || parent_recent_conf || parent_in_mempool ? 0 : 1; 236 // If we don't expect to keep the orphan then expected_parents is 0. 237 // !expect_keep_orphan => (expected_parents == 0) 238 BOOST_CHECK(expect_keep_orphan || expected_parents == 0); 239 const auto ret_1p1c = txdownload_impl.MempoolRejectedTx(orphan, state_orphan, nodeid, /*first_time_failure=*/true); 240 std::string err_msg; 241 const bool ok = CheckOrphanBehavior(txdownload_impl, orphan, ret_1p1c, err_msg, 242 /*expect_orphan=*/expect_keep_orphan, /*expect_keep=*/true, /*expected_parents=*/expected_parents); 243 BOOST_CHECK_MESSAGE(ok, err_msg); 244 } 245 246 // Orphan with multiple parents 247 { 248 std::vector<CTransactionRef> parents; 249 std::vector<COutPoint> outpoints; 250 int32_t num_parents{24}; 251 for (int32_t i = 0; i < num_parents; ++i) { 252 assert(coinbase_idx < m_coinbase_txns.size()); 253 auto mtx_parent = CreateValidMempoolTransaction(m_coinbase_txns[coinbase_idx++], /*input_vout=*/0, test_chain_height, 254 coinbaseKey, destination, amount_depth_1 + i, /*submit=*/false); 255 auto ptx_parent = MakeTransactionRef(mtx_parent); 256 parents.emplace_back(ptx_parent); 257 outpoints.emplace_back(ptx_parent->GetHash(), 0); 258 } 259 260 // Send all coins to 1 output. 261 auto mtx_orphan = CreateValidMempoolTransaction(parents, outpoints, test_chain_height, {wallet_key}, {{amount_depth_2 * num_parents, destination}}, /*submit=*/false); 262 auto orphan = MakeTransactionRef(mtx_orphan); 263 264 // 1 parent in RecentRejectsReconsiderableFilter, the rest are unknown 265 { 266 node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS}; 267 txdownload_impl.ConnectedPeer(nodeid, DEFAULT_CONN); 268 269 txdownload_impl.RecentRejectsReconsiderableFilter().insert(parents[0]->GetHash().ToUint256()); 270 const auto ret_1p1c_parent_reconsiderable = txdownload_impl.MempoolRejectedTx(orphan, state_orphan, nodeid, /*first_time_failure=*/true); 271 std::string err_msg; 272 const bool ok = CheckOrphanBehavior(txdownload_impl, orphan, ret_1p1c_parent_reconsiderable, err_msg, 273 /*expect_orphan=*/true, /*expect_keep=*/true, /*expected_parents=*/num_parents); 274 BOOST_CHECK_MESSAGE(ok, err_msg); 275 } 276 277 // 1 parent in RecentRejectsReconsiderableFilter, the rest are confirmed 278 { 279 node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS}; 280 txdownload_impl.ConnectedPeer(nodeid, DEFAULT_CONN); 281 282 txdownload_impl.RecentRejectsReconsiderableFilter().insert(parents[0]->GetHash().ToUint256()); 283 for (int32_t i = 1; i < num_parents; ++i) { 284 txdownload_impl.RecentConfirmedTransactionsFilter().insert(parents[i]->GetHash().ToUint256()); 285 } 286 const unsigned int expected_parents = 1; 287 288 const auto ret_1recon_conf = txdownload_impl.MempoolRejectedTx(orphan, state_orphan, nodeid, /*first_time_failure=*/true); 289 std::string err_msg; 290 const bool ok = CheckOrphanBehavior(txdownload_impl, orphan, ret_1recon_conf, err_msg, 291 /*expect_orphan=*/true, /*expect_keep=*/true, /*expected_parents=*/expected_parents); 292 BOOST_CHECK_MESSAGE(ok, err_msg); 293 } 294 295 // 1 parent in RecentRejectsReconsiderableFilter, 1 other in {RecentRejectsReconsiderableFilter, RecentRejectsFilter} 296 for (int i = 0; i < 2; ++i) { 297 node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS}; 298 txdownload_impl.ConnectedPeer(nodeid, DEFAULT_CONN); 299 300 txdownload_impl.RecentRejectsReconsiderableFilter().insert(parents[1]->GetHash().ToUint256()); 301 302 // Doesn't really matter which parent 303 auto& alreadyhave_parent = parents[0]; 304 if (i == 0) { 305 txdownload_impl.RecentRejectsReconsiderableFilter().insert(alreadyhave_parent->GetHash().ToUint256()); 306 } else if (i == 1) { 307 txdownload_impl.RecentRejectsFilter().insert(alreadyhave_parent->GetHash().ToUint256()); 308 } 309 310 const auto ret_2_problems = txdownload_impl.MempoolRejectedTx(orphan, state_orphan, nodeid, /*first_time_failure=*/true); 311 std::string err_msg; 312 const bool ok = CheckOrphanBehavior(txdownload_impl, orphan, ret_2_problems, err_msg, 313 /*expect_orphan=*/false, /*expect_keep=*/true, /*expected_parents=*/0); 314 BOOST_CHECK_MESSAGE(ok, err_msg); 315 } 316 } 317 318 // Orphan with multiple inputs spending from a single parent 319 { 320 assert(coinbase_idx < m_coinbase_txns.size()); 321 auto parent_2outputs = MakeTransactionRef(CreateValidMempoolTransaction({m_coinbase_txns[coinbase_idx]}, {{m_coinbase_txns[coinbase_idx]->GetHash(), 0}}, test_chain_height, {coinbaseKey}, 322 {{amount_split_half, destination}, {amount_split_half, destination}}, /*submit=*/false)); 323 324 auto orphan = MakeTransactionRef(CreateValidMempoolTransaction({parent_2outputs}, {{parent_2outputs->GetHash(), 0}, {parent_2outputs->GetHash(), 1}}, 325 test_chain_height, {wallet_key}, {{amount_depth_2, destination}}, /*submit=*/false)); 326 // Parent is in RecentRejectsReconsiderableFilter. Inputs will find it twice, but this 327 // should only counts as 1 parent in the filter. 328 { 329 node::TxDownloadManagerImpl txdownload_impl{DEFAULT_OPTS}; 330 txdownload_impl.ConnectedPeer(nodeid, DEFAULT_CONN); 331 332 txdownload_impl.RecentRejectsReconsiderableFilter().insert(parent_2outputs->GetHash().ToUint256()); 333 const auto ret_1p1c_2reconsiderable = txdownload_impl.MempoolRejectedTx(orphan, state_orphan, nodeid, /*first_time_failure=*/true); 334 std::string err_msg; 335 const bool ok = CheckOrphanBehavior(txdownload_impl, orphan, ret_1p1c_2reconsiderable, err_msg, 336 /*expect_orphan=*/true, /*expect_keep=*/true, /*expected_parents=*/1); 337 BOOST_CHECK_MESSAGE(ok, err_msg); 338 } 339 } 340 } 341 342 BOOST_AUTO_TEST_SUITE_END()