txdownload_tests.cpp raw

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