txvalidation_tests.cpp raw

   1  // Copyright (c) 2017-2021 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 <consensus/validation.h>
   6  #include <key_io.h>
   7  #include <policy/packages.h>
   8  #include <policy/policy.h>
   9  #include <policy/ephemeral_policy.h>
  10  #include <policy/truc_policy.h>
  11  #include <primitives/transaction.h>
  12  #include <random.h>
  13  #include <script/script.h>
  14  #include <test/util/setup_common.h>
  15  #include <test/util/transaction_utils.h>
  16  #include <test/util/txmempool.h>
  17  #include <validation.h>
  18  
  19  #include <boost/test/unit_test.hpp>
  20  
  21  
  22  BOOST_AUTO_TEST_SUITE(txvalidation_tests)
  23  
  24  std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx, const CTxMemPool::setEntries& mempool_ancestors, const std::set<Txid>& direct_conflicts, int64_t vsize)
  25  {
  26      std::string dummy;
  27      return SingleTRUCChecks(ptx, dummy, dummy, empty_ignore_rejects, mempool_ancestors, direct_conflicts, vsize);
  28  }
  29  
  30  std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize, const Package& package, const CTxMemPool::setEntries& mempool_ancestors)
  31  {
  32      std::string dummy;
  33      return PackageTRUCChecks(ptx, vsize, dummy, dummy, empty_ignore_rejects, package, mempool_ancestors);
  34  }
  35  
  36  /**
  37   * Ensure that the mempool won't accept coinbase transactions.
  38   */
  39  BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
  40  {
  41      CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
  42      CMutableTransaction coinbaseTx;
  43  
  44      coinbaseTx.version = 1;
  45      coinbaseTx.vin.resize(1);
  46      coinbaseTx.vout.resize(1);
  47      coinbaseTx.vin[0].scriptSig = CScript() << OP_11 << OP_EQUAL;
  48      coinbaseTx.vout[0].nValue = 1 * CENT;
  49      coinbaseTx.vout[0].scriptPubKey = scriptPubKey;
  50  
  51      BOOST_CHECK(CTransaction(coinbaseTx).IsCoinBase());
  52  
  53      LOCK(cs_main);
  54  
  55      unsigned int initialPoolSize = m_node.mempool->size();
  56      const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(coinbaseTx));
  57  
  58      BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID);
  59  
  60      // Check that the transaction hasn't been added to mempool.
  61      BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
  62  
  63      // Check that the validation state reflects the unsuccessful attempt.
  64      BOOST_CHECK(result.m_state.IsInvalid());
  65      BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");
  66      BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);
  67  }
  68  
  69  // Generate a number of random, nonexistent outpoints.
  70  static inline std::vector<COutPoint> random_outpoints(size_t num_outpoints) {
  71      std::vector<COutPoint> outpoints;
  72      for (size_t i{0}; i < num_outpoints; ++i) {
  73          outpoints.emplace_back(Txid::FromUint256(GetRandHash()), 0);
  74      }
  75      return outpoints;
  76  }
  77  
  78  static inline std::vector<CPubKey> random_keys(size_t num_keys) {
  79      std::vector<CPubKey> keys;
  80      keys.reserve(num_keys);
  81      for (size_t i{0}; i < num_keys; ++i) {
  82          CKey key;
  83          key.MakeNewKey(true);
  84          keys.emplace_back(key.GetPubKey());
  85      }
  86      return keys;
  87  }
  88  
  89  // Creates a placeholder tx (not valid) with 25 outputs. Specify the version and the inputs.
  90  static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, int32_t version)
  91  {
  92      CMutableTransaction mtx = CMutableTransaction{};
  93      mtx.version = version;
  94      mtx.vin.resize(inputs.size());
  95      mtx.vout.resize(25);
  96      for (size_t i{0}; i < inputs.size(); ++i) {
  97          mtx.vin[i].prevout = inputs[i];
  98      }
  99      for (auto i{0}; i < 25; ++i) {
 100          mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;
 101          mtx.vout[i].nValue = 10000;
 102      }
 103      return MakeTransactionRef(mtx);
 104  }
 105  
 106  static constexpr auto NUM_EPHEMERAL_TX_OUTPUTS = 3;
 107  static constexpr auto EPHEMERAL_DUST_INDEX = NUM_EPHEMERAL_TX_OUTPUTS - 1;
 108  
 109  // Same as make_tx but adds 2 normal outputs and 0-value dust to end of vout
 110  static inline CTransactionRef make_ephemeral_tx(const std::vector<COutPoint>& inputs, int32_t version)
 111  {
 112      CMutableTransaction mtx = CMutableTransaction{};
 113      mtx.version = version;
 114      mtx.vin.resize(inputs.size());
 115      for (size_t i{0}; i < inputs.size(); ++i) {
 116          mtx.vin[i].prevout = inputs[i];
 117      }
 118      mtx.vout.resize(NUM_EPHEMERAL_TX_OUTPUTS);
 119      for (auto i{0}; i < NUM_EPHEMERAL_TX_OUTPUTS; ++i) {
 120          mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;
 121          mtx.vout[i].nValue = (i == EPHEMERAL_DUST_INDEX) ? 0 : 10000;
 122      }
 123      return MakeTransactionRef(mtx);
 124  }
 125  
 126  BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
 127  {
 128      CTxMemPool& pool = *Assert(m_node.mempool);
 129      LOCK2(cs_main, pool.cs);
 130      TestMemPoolEntryHelper entry;
 131      CTxMemPool::setEntries empty_ancestors;
 132  
 133      TxValidationState child_state;
 134      Wtxid child_wtxid;
 135  
 136      // Arbitrary non-0 feerate for these tests
 137      CFeeRate dustrelay(DUST_RELAY_TX_FEE);
 138  
 139      // Basic transaction with dust
 140      auto grandparent_tx_1 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);
 141      const auto dust_txid = grandparent_tx_1->GetHash();
 142  
 143      // Child transaction spending dust
 144      auto dust_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
 145  
 146      // We first start with nothing "in the mempool", using package checks
 147  
 148      // Trivial single transaction with no dust
 149      BOOST_CHECK(CheckEphemeralSpends({dust_spend}, dustrelay, pool, child_state, child_wtxid));
 150      BOOST_CHECK(child_state.IsValid());
 151      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 152  
 153      // Now with dust, ok because the tx has no dusty parents
 154      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));
 155      BOOST_CHECK(child_state.IsValid());
 156      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 157  
 158      // Dust checks pass
 159      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, CFeeRate(0), pool, child_state, child_wtxid));
 160      BOOST_CHECK(child_state.IsValid());
 161      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 162      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, dustrelay, pool, child_state, child_wtxid));
 163      BOOST_CHECK(child_state.IsValid());
 164      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 165  
 166      auto dust_non_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);
 167  
 168      // Child spending non-dust only from parent should be disallowed even if dust otherwise spent
 169      const auto dust_non_spend_wtxid{dust_non_spend->GetWitnessHash()};
 170      BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend, dust_spend}, dustrelay, pool, child_state, child_wtxid));
 171      BOOST_CHECK(!child_state.IsValid());
 172      BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
 173      child_state = TxValidationState();
 174      child_wtxid = Wtxid();
 175  
 176      BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_spend, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));
 177      BOOST_CHECK(!child_state.IsValid());
 178      BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
 179      child_state = TxValidationState();
 180      child_wtxid = Wtxid();
 181  
 182      BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));
 183      BOOST_CHECK(!child_state.IsValid());
 184      BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
 185      child_state = TxValidationState();
 186      child_wtxid = Wtxid();
 187  
 188      auto grandparent_tx_2 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);
 189      const auto dust_txid_2 = grandparent_tx_2->GetHash();
 190  
 191      // Spend dust from one but not another is ok, as long as second grandparent has no child
 192      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend}, dustrelay, pool, child_state, child_wtxid));
 193      BOOST_CHECK(child_state.IsValid());
 194      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 195  
 196      auto dust_non_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);
 197      // But if we spend from the parent, it must spend dust
 198      BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
 199      BOOST_CHECK(!child_state.IsValid());
 200      BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());
 201      child_state = TxValidationState();
 202      child_wtxid = Wtxid();
 203  
 204      auto dust_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
 205      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
 206      BOOST_CHECK(child_state.IsValid());
 207      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 208  
 209      // Spending other outputs is also correct, as long as the dusty one is spent
 210      const std::vector<COutPoint> all_outpoints{COutPoint(dust_txid, 0), COutPoint(dust_txid, 1), COutPoint(dust_txid, 2),
 211          COutPoint(dust_txid_2, 0), COutPoint(dust_txid_2, 1), COutPoint(dust_txid_2, 2)};
 212      auto dust_spend_all_outpoints = make_tx(all_outpoints, /*version=*/2);
 213      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_all_outpoints}, dustrelay, pool, child_state, child_wtxid));
 214      BOOST_CHECK(child_state.IsValid());
 215      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 216  
 217      // 2 grandparents with dust <- 1 dust-spending parent with dust <- child with no dust
 218      auto parent_with_dust = make_ephemeral_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
 219      // Ok for parent to have dust
 220      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
 221      BOOST_CHECK(child_state.IsValid());
 222      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 223      auto child_no_dust = make_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);
 224      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_no_dust}, dustrelay, pool, child_state, child_wtxid));
 225      BOOST_CHECK(child_state.IsValid());
 226      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 227  
 228      // 2 grandparents with dust <- 1 dust-spending parent with dust <- child with dust
 229      auto child_with_dust = make_ephemeral_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);
 230      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_with_dust}, dustrelay, pool, child_state, child_wtxid));
 231      BOOST_CHECK(child_state.IsValid());
 232      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 233  
 234      // Tests with parents in mempool
 235  
 236      // Nothing in mempool, this should pass for any transaction
 237      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));
 238      BOOST_CHECK(child_state.IsValid());
 239      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 240  
 241      // Add first grandparent to mempool and fetch entry
 242      AddToMempool(pool, entry.FromTx(grandparent_tx_1));
 243  
 244      // Ignores ancestors that aren't direct parents
 245      BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));
 246      BOOST_CHECK(child_state.IsValid());
 247      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 248  
 249      // Valid spend of dust with grandparent in mempool
 250      BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
 251      BOOST_CHECK(child_state.IsValid());
 252      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 253  
 254      // Second grandparent in same package
 255      BOOST_CHECK(CheckEphemeralSpends({parent_with_dust, grandparent_tx_2}, dustrelay, pool, child_state, child_wtxid));
 256      BOOST_CHECK(child_state.IsValid());
 257      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 258  
 259      // Order in package doesn't matter
 260      BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
 261      BOOST_CHECK(child_state.IsValid());
 262      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 263  
 264      // Add second grandparent to mempool
 265      AddToMempool(pool, entry.FromTx(grandparent_tx_2));
 266  
 267      // Only spends single dust out of two direct parents
 268      BOOST_CHECK(!CheckEphemeralSpends({dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
 269      BOOST_CHECK(!child_state.IsValid());
 270      BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());
 271      child_state = TxValidationState();
 272      child_wtxid = Wtxid();
 273  
 274      // Spends both parents' dust
 275      BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
 276      BOOST_CHECK(child_state.IsValid());
 277      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 278  
 279      // Now add dusty parent to mempool
 280      AddToMempool(pool, entry.FromTx(parent_with_dust));
 281  
 282      // Passes dust checks even with non-parent ancestors
 283      BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));
 284      BOOST_CHECK(child_state.IsValid());
 285      BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
 286  }
 287  
 288  BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
 289  {
 290      // Test TRUC policy helper functions
 291      CTxMemPool& pool = *Assert(m_node.mempool);
 292      LOCK2(cs_main, pool.cs);
 293      TestMemPoolEntryHelper entry;
 294      std::set<Txid> empty_conflicts_set;
 295      CTxMemPool::setEntries empty_ancestors;
 296  
 297      auto mempool_tx_v3 = make_tx(random_outpoints(1), /*version=*/3);
 298      AddToMempool(pool, entry.FromTx(mempool_tx_v3));
 299      auto mempool_tx_v2 = make_tx(random_outpoints(1), /*version=*/2);
 300      AddToMempool(pool, entry.FromTx(mempool_tx_v2));
 301      // Default values.
 302      CTxMemPool::Limits m_limits{};
 303  
 304      // Cannot spend from an unconfirmed TRUC transaction unless this tx is also TRUC.
 305      {
 306          // mempool_tx_v3
 307          //      ^
 308          // tx_v2_from_v3
 309          auto tx_v2_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/2);
 310          auto ancestors_v2_from_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v3), m_limits)};
 311          const auto expected_error_str{strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
 312              tx_v2_from_v3->GetHash().ToString(), tx_v2_from_v3->GetWitnessHash().ToString(),
 313              mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
 314          auto result_v2_from_v3{SingleTRUCChecks(tx_v2_from_v3, *ancestors_v2_from_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v3))};
 315          BOOST_CHECK_EQUAL(result_v2_from_v3->first, expected_error_str);
 316          BOOST_CHECK_EQUAL(result_v2_from_v3->second, nullptr);
 317  
 318          Package package_v3_v2{mempool_tx_v3, tx_v2_from_v3};
 319          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), package_v3_v2, empty_ancestors), expected_error_str);
 320          CTxMemPool::setEntries entries_mempool_v3{pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value()};
 321          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), {tx_v2_from_v3}, entries_mempool_v3), expected_error_str);
 322  
 323          // mempool_tx_v3  mempool_tx_v2
 324          //            ^    ^
 325          //    tx_v2_from_v2_and_v3
 326          auto tx_v2_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
 327          auto ancestors_v2_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2_and_v3), m_limits)};
 328          const auto expected_error_str_2{strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
 329              tx_v2_from_v2_and_v3->GetHash().ToString(), tx_v2_from_v2_and_v3->GetWitnessHash().ToString(),
 330              mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
 331          auto result_v2_from_both{SingleTRUCChecks(tx_v2_from_v2_and_v3, *ancestors_v2_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3))};
 332          BOOST_CHECK_EQUAL(result_v2_from_both->first, expected_error_str_2);
 333          BOOST_CHECK_EQUAL(result_v2_from_both->second, nullptr);
 334  
 335          Package package_v3_v2_v2{mempool_tx_v3, mempool_tx_v2, tx_v2_from_v2_and_v3};
 336          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v2_from_v2_and_v3, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3), package_v3_v2_v2, empty_ancestors), expected_error_str_2);
 337      }
 338  
 339      // TRUC cannot spend from an unconfirmed non-TRUC transaction.
 340      {
 341          // mempool_tx_v2
 342          //      ^
 343          // tx_v3_from_v2
 344          auto tx_v3_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
 345          auto ancestors_v3_from_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2), m_limits)};
 346          const auto expected_error_str{strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
 347              tx_v3_from_v2->GetHash().ToString(), tx_v3_from_v2->GetWitnessHash().ToString(),
 348              mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
 349          auto result_v3_from_v2{SingleTRUCChecks(tx_v3_from_v2, *ancestors_v3_from_v2,  empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2))};
 350          BOOST_CHECK_EQUAL(result_v3_from_v2->first, expected_error_str);
 351          BOOST_CHECK_EQUAL(result_v3_from_v2->second, nullptr);
 352  
 353          Package package_v2_v3{mempool_tx_v2, tx_v3_from_v2};
 354          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), package_v2_v3, empty_ancestors), expected_error_str);
 355          CTxMemPool::setEntries entries_mempool_v2{pool.GetIter(mempool_tx_v2->GetHash().ToUint256()).value()};
 356          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), {tx_v3_from_v2}, entries_mempool_v2), expected_error_str);
 357  
 358          // mempool_tx_v3  mempool_tx_v2
 359          //            ^    ^
 360          //    tx_v3_from_v2_and_v3
 361          auto tx_v3_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
 362          auto ancestors_v3_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2_and_v3), m_limits)};
 363          const auto expected_error_str_2{strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
 364              tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString(),
 365              mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
 366          auto result_v3_from_both{SingleTRUCChecks(tx_v3_from_v2_and_v3, *ancestors_v3_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3))};
 367          BOOST_CHECK_EQUAL(result_v3_from_both->first, expected_error_str_2);
 368          BOOST_CHECK_EQUAL(result_v3_from_both->second, nullptr);
 369  
 370          // tx_v3_from_v2_and_v3 also violates TRUC_ANCESTOR_LIMIT.
 371          const auto expected_error_str_3{strprintf("tx %s (wtxid=%s) would have too many ancestors",
 372              tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString())};
 373          Package package_v3_v2_v3{mempool_tx_v3, mempool_tx_v2, tx_v3_from_v2_and_v3};
 374          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_from_v2_and_v3, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3), package_v3_v2_v3, empty_ancestors), expected_error_str_3);
 375      }
 376      // V3 from V3 is ok, and non-V3 from non-V3 is ok.
 377      {
 378          // mempool_tx_v3
 379          //      ^
 380          // tx_v3_from_v3
 381          auto tx_v3_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
 382          auto ancestors_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v3), m_limits)};
 383          BOOST_CHECK(SingleTRUCChecks(tx_v3_from_v3, *ancestors_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v3))
 384                      == std::nullopt);
 385  
 386          Package package_v3_v3{mempool_tx_v3, tx_v3_from_v3};
 387          BOOST_CHECK(PackageTRUCChecks(tx_v3_from_v3, GetVirtualTransactionSize(*tx_v3_from_v3), package_v3_v3, empty_ancestors) == std::nullopt);
 388  
 389          // mempool_tx_v2
 390          //      ^
 391          // tx_v2_from_v2
 392          auto tx_v2_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
 393          auto ancestors_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2), m_limits)};
 394          BOOST_CHECK(SingleTRUCChecks(tx_v2_from_v2, *ancestors_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2))
 395                      == std::nullopt);
 396  
 397          Package package_v2_v2{mempool_tx_v2, tx_v2_from_v2};
 398          BOOST_CHECK(PackageTRUCChecks(tx_v2_from_v2, GetVirtualTransactionSize(*tx_v2_from_v2), package_v2_v2, empty_ancestors) == std::nullopt);
 399      }
 400  
 401      // Tx spending TRUC cannot have too many mempool ancestors
 402      // Configuration where the tx has multiple direct parents.
 403      {
 404          Package package_multi_parents;
 405          std::vector<COutPoint> mempool_outpoints;
 406          mempool_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
 407          package_multi_parents.emplace_back(mempool_tx_v3);
 408          for (size_t i{0}; i < 2; ++i) {
 409              auto mempool_tx = make_tx(random_outpoints(i + 1), /*version=*/3);
 410              AddToMempool(pool, entry.FromTx(mempool_tx));
 411              mempool_outpoints.emplace_back(mempool_tx->GetHash(), 0);
 412              package_multi_parents.emplace_back(mempool_tx);
 413          }
 414          auto tx_v3_multi_parent = make_tx(mempool_outpoints, /*version=*/3);
 415          package_multi_parents.emplace_back(tx_v3_multi_parent);
 416          auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_parent), m_limits)};
 417          BOOST_CHECK_EQUAL(ancestors->size(), 3);
 418          const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
 419              tx_v3_multi_parent->GetHash().ToString(), tx_v3_multi_parent->GetWitnessHash().ToString())};
 420          auto result{SingleTRUCChecks(tx_v3_multi_parent, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_parent))};
 421          BOOST_CHECK_EQUAL(result->first, expected_error_str);
 422          BOOST_CHECK_EQUAL(result->second, nullptr);
 423  
 424          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_multi_parent, GetVirtualTransactionSize(*tx_v3_multi_parent), package_multi_parents, empty_ancestors),
 425                            expected_error_str);
 426      }
 427  
 428      // Configuration where the tx is in a multi-generation chain.
 429      {
 430          Package package_multi_gen;
 431          CTransactionRef middle_tx;
 432          auto last_outpoint{random_outpoints(1)[0]};
 433          for (size_t i{0}; i < 2; ++i) {
 434              auto mempool_tx = make_tx({last_outpoint}, /*version=*/3);
 435              AddToMempool(pool, entry.FromTx(mempool_tx));
 436              last_outpoint = COutPoint{mempool_tx->GetHash(), 0};
 437              package_multi_gen.emplace_back(mempool_tx);
 438              if (i == 1) middle_tx = mempool_tx;
 439          }
 440          auto tx_v3_multi_gen = make_tx({last_outpoint}, /*version=*/3);
 441          package_multi_gen.emplace_back(tx_v3_multi_gen);
 442          auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_gen), m_limits)};
 443          const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
 444              tx_v3_multi_gen->GetHash().ToString(), tx_v3_multi_gen->GetWitnessHash().ToString())};
 445          auto result{SingleTRUCChecks(tx_v3_multi_gen, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_gen))};
 446          BOOST_CHECK_EQUAL(result->first, expected_error_str);
 447          BOOST_CHECK_EQUAL(result->second, nullptr);
 448  
 449          // Middle tx is what triggers a failure for the grandchild:
 450          BOOST_CHECK_EQUAL(*PackageTRUCChecks(middle_tx, GetVirtualTransactionSize(*middle_tx), package_multi_gen, empty_ancestors), expected_error_str);
 451          BOOST_CHECK(PackageTRUCChecks(tx_v3_multi_gen, GetVirtualTransactionSize(*tx_v3_multi_gen), package_multi_gen, empty_ancestors) == std::nullopt);
 452      }
 453  
 454      // Tx spending TRUC cannot be too large in virtual size.
 455      auto many_inputs{random_outpoints(100)};
 456      many_inputs.emplace_back(mempool_tx_v3->GetHash(), 0);
 457      {
 458          auto tx_v3_child_big = make_tx(many_inputs, /*version=*/3);
 459          const auto vsize{GetVirtualTransactionSize(*tx_v3_child_big)};
 460          auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child_big), m_limits)};
 461          const auto expected_error_str{strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
 462              tx_v3_child_big->GetHash().ToString(), tx_v3_child_big->GetWitnessHash().ToString(), vsize, TRUC_CHILD_MAX_VSIZE)};
 463          auto result{SingleTRUCChecks(tx_v3_child_big, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child_big))};
 464          BOOST_CHECK_EQUAL(result->first, expected_error_str);
 465          BOOST_CHECK_EQUAL(result->second, nullptr);
 466  
 467          Package package_child_big{mempool_tx_v3, tx_v3_child_big};
 468          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_child_big, GetVirtualTransactionSize(*tx_v3_child_big), package_child_big, empty_ancestors),
 469                            expected_error_str);
 470      }
 471  
 472      // Tx spending TRUC cannot have too many sigops.
 473      // This child has 10 P2WSH multisig inputs.
 474      auto multisig_outpoints{random_outpoints(10)};
 475      multisig_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
 476      auto keys{random_keys(2)};
 477      CScript script_multisig;
 478      script_multisig << OP_1;
 479      for (const auto& key : keys) {
 480          script_multisig << ToByteVector(key);
 481      }
 482      script_multisig << OP_2 << OP_CHECKMULTISIG;
 483      {
 484          CMutableTransaction mtx_many_sigops = CMutableTransaction{};
 485          mtx_many_sigops.version = TRUC_VERSION;
 486          for (const auto& outpoint : multisig_outpoints) {
 487              mtx_many_sigops.vin.emplace_back(outpoint);
 488              mtx_many_sigops.vin.back().scriptWitness.stack.emplace_back(script_multisig.begin(), script_multisig.end());
 489          }
 490          mtx_many_sigops.vout.resize(1);
 491          mtx_many_sigops.vout.back().scriptPubKey = CScript() << OP_TRUE;
 492          mtx_many_sigops.vout.back().nValue = 10000;
 493          auto tx_many_sigops{MakeTransactionRef(mtx_many_sigops)};
 494  
 495          auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_many_sigops), m_limits)};
 496          // legacy uses fAccurate = false, and the maximum number of multisig keys is used
 497          const int64_t total_sigops{static_cast<int64_t>(tx_many_sigops->vin.size()) * static_cast<int64_t>(script_multisig.GetSigOpCount(/*fAccurate=*/false))};
 498          BOOST_CHECK_EQUAL(total_sigops, tx_many_sigops->vin.size() * MAX_PUBKEYS_PER_MULTISIG);
 499          const int64_t bip141_vsize{GetVirtualTransactionSize(*tx_many_sigops)};
 500          // Weight limit is not reached...
 501          BOOST_CHECK(SingleTRUCChecks(tx_many_sigops, *ancestors, empty_conflicts_set, bip141_vsize) == std::nullopt);
 502          // ...but sigop limit is.
 503          const auto expected_error_str{strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
 504              tx_many_sigops->GetHash().ToString(), tx_many_sigops->GetWitnessHash().ToString(),
 505              total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, TRUC_CHILD_MAX_VSIZE)};
 506          auto result{SingleTRUCChecks(tx_many_sigops, *ancestors, empty_conflicts_set,
 507                                          GetVirtualTransactionSize(*tx_many_sigops, /*nSigOpCost=*/total_sigops, /*bytes_per_sigop=*/ DEFAULT_BYTES_PER_SIGOP))};
 508          BOOST_CHECK_EQUAL(result->first, expected_error_str);
 509          BOOST_CHECK_EQUAL(result->second, nullptr);
 510  
 511          Package package_child_sigops{mempool_tx_v3, tx_many_sigops};
 512          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_many_sigops, total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, package_child_sigops, empty_ancestors),
 513                            expected_error_str);
 514      }
 515  
 516      // Parent + child with TRUC in the mempool. Child is allowed as long as it is under TRUC_CHILD_MAX_VSIZE.
 517      auto tx_mempool_v3_child = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
 518      {
 519          BOOST_CHECK(GetTransactionWeight(*tx_mempool_v3_child) <= TRUC_CHILD_MAX_VSIZE * WITNESS_SCALE_FACTOR);
 520          auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_mempool_v3_child), m_limits)};
 521          BOOST_CHECK(SingleTRUCChecks(tx_mempool_v3_child, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_mempool_v3_child)) == std::nullopt);
 522          AddToMempool(pool, entry.FromTx(tx_mempool_v3_child));
 523  
 524          Package package_v3_1p1c{mempool_tx_v3, tx_mempool_v3_child};
 525          BOOST_CHECK(PackageTRUCChecks(tx_mempool_v3_child, GetVirtualTransactionSize(*tx_mempool_v3_child), package_v3_1p1c, empty_ancestors) == std::nullopt);
 526      }
 527  
 528      // A TRUC transaction cannot have more than 1 descendant. Sibling is returned when exactly 1 exists.
 529      {
 530          auto tx_v3_child2 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 1}}, /*version=*/3);
 531  
 532          // Configuration where parent already has 1 other child in mempool
 533          auto ancestors_1sibling{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child2), m_limits)};
 534          const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
 535              mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
 536          auto result_with_sibling_eviction{SingleTRUCChecks(tx_v3_child2, *ancestors_1sibling, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child2))};
 537          BOOST_CHECK_EQUAL(result_with_sibling_eviction->first, expected_error_str);
 538          // The other mempool child is returned to allow for sibling eviction.
 539          BOOST_CHECK_EQUAL(result_with_sibling_eviction->second, tx_mempool_v3_child);
 540  
 541          // If directly replacing the child, make sure there is no double-counting.
 542          BOOST_CHECK(SingleTRUCChecks(tx_v3_child2, *ancestors_1sibling, {tx_mempool_v3_child->GetHash()}, GetVirtualTransactionSize(*tx_v3_child2))
 543                      == std::nullopt);
 544  
 545          Package package_v3_1p2c{mempool_tx_v3, tx_mempool_v3_child, tx_v3_child2};
 546          BOOST_CHECK_EQUAL(*PackageTRUCChecks(tx_v3_child2, GetVirtualTransactionSize(*tx_v3_child2), package_v3_1p2c, empty_ancestors),
 547                            expected_error_str);
 548  
 549          // Configuration where parent already has 2 other children in mempool (no sibling eviction allowed). This may happen as the result of a reorg.
 550          AddToMempool(pool, entry.FromTx(tx_v3_child2));
 551          auto tx_v3_child3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 24}}, /*version=*/3);
 552          auto entry_mempool_parent = pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value();
 553          BOOST_CHECK_EQUAL(entry_mempool_parent->GetCountWithDescendants(), 3);
 554          auto ancestors_2siblings{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child3), m_limits)};
 555  
 556          auto result_2children{SingleTRUCChecks(tx_v3_child3, *ancestors_2siblings, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child3))};
 557          BOOST_CHECK_EQUAL(result_2children->first, expected_error_str);
 558          // The other mempool child is not returned because sibling eviction is not allowed.
 559          BOOST_CHECK_EQUAL(result_2children->second, nullptr);
 560      }
 561  
 562      // Sibling eviction: parent already has 1 other child, which also has its own child (no sibling eviction allowed). This may happen as the result of a reorg.
 563      {
 564          auto tx_mempool_grandparent = make_tx(random_outpoints(1), /*version=*/3);
 565          auto tx_mempool_sibling = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 0}}, /*version=*/3);
 566          auto tx_mempool_nibling = make_tx({COutPoint{tx_mempool_sibling->GetHash(), 0}}, /*version=*/3);
 567          auto tx_to_submit = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 1}}, /*version=*/3);
 568  
 569          AddToMempool(pool, entry.FromTx(tx_mempool_grandparent));
 570          AddToMempool(pool, entry.FromTx(tx_mempool_sibling));
 571          AddToMempool(pool, entry.FromTx(tx_mempool_nibling));
 572  
 573          auto ancestors_3gen{pool.CalculateMemPoolAncestors(entry.FromTx(tx_to_submit), m_limits)};
 574          const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
 575              tx_mempool_grandparent->GetHash().ToString(), tx_mempool_grandparent->GetWitnessHash().ToString())};
 576          auto result_3gen{SingleTRUCChecks(tx_to_submit, *ancestors_3gen, empty_conflicts_set, GetVirtualTransactionSize(*tx_to_submit))};
 577          BOOST_CHECK_EQUAL(result_3gen->first, expected_error_str);
 578          // The other mempool child is not returned because sibling eviction is not allowed.
 579          BOOST_CHECK_EQUAL(result_3gen->second, nullptr);
 580      }
 581  
 582      // Configuration where tx has multiple generations of descendants is not tested because that is
 583      // equivalent to the tx with multiple generations of ancestors.
 584  }
 585  
 586  BOOST_AUTO_TEST_SUITE_END()
 587