package_eval.cpp raw

   1  // Copyright (c) 2023-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 <chain.h>
   6  #include <consensus/amount.h>
   7  #include <consensus/consensus.h>
   8  #include <consensus/validation.h>
   9  #include <node/mining_types.h>
  10  #include <policy/feerate.h>
  11  #include <policy/packages.h>
  12  #include <policy/policy.h>
  13  #include <policy/settings.h>
  14  #include <policy/truc_policy.h>
  15  #include <primitives/transaction.h>
  16  #include <script/script.h>
  17  #include <sync.h>
  18  #include <test/fuzz/FuzzedDataProvider.h>
  19  #include <test/fuzz/fuzz.h>
  20  #include <test/fuzz/util.h>
  21  #include <test/fuzz/util/mempool.h>
  22  #include <test/util/mining.h>
  23  #include <test/util/random.h>
  24  #include <test/util/script.h>
  25  #include <test/util/setup_common.h>
  26  #include <test/util/txmempool.h>
  27  #include <txmempool.h>
  28  #include <util/check.h>
  29  #include <util/hasher.h>
  30  #include <util/time.h>
  31  #include <util/translation.h>
  32  #include <validation.h>
  33  #include <validationinterface.h>
  34  
  35  #include <cstddef>
  36  #include <cstdint>
  37  #include <functional>
  38  #include <iterator>
  39  #include <limits>
  40  #include <map>
  41  #include <memory>
  42  #include <optional>
  43  #include <set>
  44  #include <span>
  45  #include <string>
  46  #include <unordered_map>
  47  #include <utility>
  48  #include <vector>
  49  using node::NodeContext;
  50  
  51  namespace {
  52  
  53  const TestingSetup* g_setup;
  54  std::vector<COutPoint> g_outpoints_coinbase_init_mature;
  55  
  56  struct MockedTxPool : public CTxMemPool {
  57      void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)
  58      {
  59          LOCK(cs);
  60          lastRollingFeeUpdate = GetTime();
  61          blockSinceLastRollingFeeBump = true;
  62      }
  63  };
  64  
  65  void initialize_tx_pool()
  66  {
  67      static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
  68      g_setup = testing_setup.get();
  69      SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time()));
  70  
  71      for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
  72          COutPoint prevout{MineBlock(g_setup->m_node, {
  73              .coinbase_output_script = P2WSH_EMPTY,
  74          })};
  75          if (i < COINBASE_MATURITY) {
  76              // Remember the txids to avoid expensive disk access later on
  77              g_outpoints_coinbase_init_mature.push_back(prevout);
  78          }
  79      }
  80      g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
  81  }
  82  
  83  struct OutpointsUpdater final : public CValidationInterface {
  84      std::set<COutPoint>& m_mempool_outpoints;
  85  
  86      explicit OutpointsUpdater(std::set<COutPoint>& r)
  87          : m_mempool_outpoints{r} {}
  88  
  89      void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
  90      {
  91          // for coins spent we always want to be able to rbf so they're not removed
  92  
  93          // outputs from this tx can now be spent
  94          for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) {
  95              m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index});
  96          }
  97      }
  98  
  99      void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
 100      {
 101          // outpoints spent by this tx are now available
 102          for (const auto& input : tx->vin) {
 103              // Could already exist if this was a replacement
 104              m_mempool_outpoints.insert(input.prevout);
 105          }
 106          // outpoints created by this tx no longer exist
 107          for (uint32_t index{0}; index < tx->vout.size(); ++index) {
 108              m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index});
 109          }
 110      }
 111  };
 112  
 113  struct TransactionsDelta final : public CValidationInterface {
 114      std::set<CTransactionRef>& m_added;
 115  
 116      explicit TransactionsDelta(std::set<CTransactionRef>& a)
 117          : m_added{a} {}
 118  
 119      void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override
 120      {
 121          // Transactions may be entered and booted any number of times
 122          m_added.insert(tx.info.m_tx);
 123      }
 124  
 125      void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override
 126      {
 127          // Transactions may be entered and booted any number of times
 128           m_added.erase(tx);
 129      }
 130  };
 131  
 132  void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
 133  {
 134      const auto time = ConsumeTime(fuzzed_data_provider,
 135                                    chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
 136                                    std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
 137      SetMockTime(time);
 138  }
 139  
 140  std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
 141  {
 142      // Take the default options for tests...
 143      CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
 144  
 145  
 146      // ...override specific options for this specific fuzz suite
 147      mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
 148      mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);
 149      mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;
 150      mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
 151      // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
 152      nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;
 153  
 154      mempool_opts.check_ratio = 1;
 155      mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
 156  
 157      bilingual_str error;
 158      // ...and construct a CTxMemPool from it
 159      auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
 160      // ... ignore the error since it might be beneficial to fuzz even when the
 161      // mempool size is unreasonably small
 162      Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));
 163      return mempool;
 164  }
 165  
 166  std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node)
 167  {
 168      // Take the default options for tests...
 169      CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
 170  
 171      mempool_opts.check_ratio = 1;
 172  
 173      // Require standardness rules otherwise ephemeral dust is no-op
 174      mempool_opts.require_standard = true;
 175  
 176      // And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC
 177      mempool_opts.min_relay_feerate = CFeeRate(0);
 178  
 179      bilingual_str error;
 180      // ...and construct a CTxMemPool from it
 181      auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};
 182      Assert(error.empty());
 183      return mempool;
 184  }
 185  
 186  // Scan mempool for a tx that has spent dust and return a
 187  // prevout of the child that isn't the dusty parent itself.
 188  // This is used to double-spend the child out of the mempool,
 189  // leaving the parent childless.
 190  // This assumes CheckMempoolEphemeralInvariants has passed for tx_pool.
 191  std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool)
 192  {
 193      LOCK(tx_pool.cs);
 194      for (const auto& tx_info : tx_pool.infoAll()) {
 195          const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
 196          std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)};
 197          if (!dust_indexes.empty()) {
 198              const auto& children = tx_pool.GetChildren(entry);
 199              if (!children.empty()) {
 200                  Assert(children.size() == 1);
 201                  // Find an input that doesn't spend from parent's txid
 202                  const auto& only_child = children.begin()->get().GetTx();
 203                  for (const auto& tx_input : only_child.vin) {
 204                      if (tx_input.prevout.hash != tx_info.tx->GetHash()) {
 205                          return tx_input.prevout;
 206                      }
 207                  }
 208              }
 209          }
 210      }
 211  
 212      return std::nullopt;
 213  }
 214  
 215  FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
 216  {
 217      SeedRandomStateForTest(SeedRand::ZEROS);
 218      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 219      const auto& node = g_setup->m_node;
 220      auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
 221  
 222      MockTime(fuzzed_data_provider, chainstate);
 223  
 224      // All RBF-spendable outpoints outside of the unsubmitted package
 225      std::set<COutPoint> mempool_outpoints;
 226      std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
 227      for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
 228          Assert(mempool_outpoints.insert(outpoint).second);
 229          outpoints_value[outpoint] = 50 * COIN;
 230      }
 231  
 232      auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
 233      node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
 234  
 235      auto tx_pool_{MakeEphemeralMempool(node)};
 236      MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
 237  
 238      chainstate.SetMempool(&tx_pool);
 239  
 240      LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
 241      {
 242          Assert(!mempool_outpoints.empty());
 243  
 244          std::vector<CTransactionRef> txs;
 245  
 246          // Find something we may want to double-spend with two input single tx
 247          std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt};
 248  
 249          // Make small packages
 250          const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4);
 251  
 252          std::set<COutPoint> package_outpoints;
 253          while (txs.size() < num_txs) {
 254              // Create transaction to add to the mempool
 255              txs.emplace_back([&] {
 256                  CMutableTransaction tx_mut;
 257                  tx_mut.version = CTransaction::CURRENT_VERSION;
 258                  tx_mut.nLockTime = 0;
 259                  // Last transaction in a package needs to be a child of parents to get further in validation
 260                  // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
 261                  // Note that this test currently only spends package outputs in last transaction.
 262                  bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
 263                  const auto num_in = outpoint_to_rbf ? 2 :
 264                      last_tx ? fuzzed_data_provider.ConsumeIntegralInRange<int>(package_outpoints.size()/2 + 1, package_outpoints.size()) :
 265                      fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
 266                  const auto num_out = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 4);
 267  
 268                  auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
 269  
 270                  Assert((int)outpoints.size() >= num_in && num_in > 0);
 271  
 272                  CAmount amount_in{0};
 273                  for (int i = 0; i < num_in; ++i) {
 274                      // Pop random outpoint. We erase them to avoid double-spending
 275                      // while in this loop, but later add them back (unless last_tx).
 276                      auto pop = outpoints.begin();
 277                      std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
 278                      auto outpoint = *pop;
 279  
 280                      if (i == 0 && outpoint_to_rbf) {
 281                          outpoint = *outpoint_to_rbf;
 282                          outpoints.erase(outpoint);
 283                      } else {
 284                          outpoints.erase(pop);
 285                      }
 286                      // no need to update or erase from outpoints_value
 287                      amount_in += outpoints_value.at(outpoint);
 288  
 289                      // Create input
 290                      CTxIn in;
 291                      in.prevout = outpoint;
 292                      in.scriptWitness.stack = P2WSH_EMPTY_TRUE_STACK;
 293  
 294                      tx_mut.vin.push_back(in);
 295                  }
 296  
 297                  const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
 298                  const auto amount_out = (amount_in - amount_fee) / num_out;
 299                  for (int i = 0; i < num_out; ++i) {
 300                      tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
 301                  }
 302  
 303                  // Note output amounts can naturally drop to dust on their own.
 304                  if (!outpoint_to_rbf && fuzzed_data_provider.ConsumeBool()) {
 305                      uint32_t dust_index = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_out);
 306                      tx_mut.vout.insert(tx_mut.vout.begin() + dust_index, CTxOut(0, P2WSH_EMPTY));
 307                  }
 308  
 309                  auto tx = MakeTransactionRef(tx_mut);
 310                  // Restore previously removed outpoints, except in-package outpoints (to allow RBF)
 311                  if (!last_tx) {
 312                      for (const auto& in : tx->vin) {
 313                          Assert(outpoints.insert(in.prevout).second);
 314                      }
 315                      // Cache the in-package outpoints being made
 316                      for (size_t i = 0; i < tx->vout.size(); ++i) {
 317                          package_outpoints.emplace(tx->GetHash(), i);
 318                      }
 319                  }
 320                  // We need newly-created values for the duration of this run
 321                  for (size_t i = 0; i < tx->vout.size(); ++i) {
 322                      outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
 323                  }
 324                  return tx;
 325              }());
 326          }
 327  
 328          if (fuzzed_data_provider.ConsumeBool()) {
 329              const auto& txid = fuzzed_data_provider.ConsumeBool() ?
 330                                     txs.back()->GetHash() :
 331                                     PickValue(fuzzed_data_provider, mempool_outpoints).hash;
 332              const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
 333              // We only prioritise out of mempool transactions since PrioritiseTransaction doesn't
 334              // filter for ephemeral dust
 335              if (tx_pool.exists(txid)) {
 336                  const auto tx_info{tx_pool.info(txid)};
 337                  if (GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate).empty()) {
 338                      tx_pool.PrioritiseTransaction(txid, delta);
 339                  }
 340              }
 341          }
 342  
 343          auto single_submit = txs.size() == 1;
 344  
 345          const auto result_package = WITH_LOCK(::cs_main,
 346                                      return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
 347  
 348          const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
 349                                     /*bypass_limits=*/false, /*test_accept=*/!single_submit));
 350  
 351          if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
 352              // We don't know anything about the validity since transactions were randomly generated, so
 353              // just use result_package.m_state here. This makes the expect_valid check meaningless, but
 354              // we can still verify that the contents of m_tx_results are consistent with m_state.
 355              const bool expect_valid{result_package.m_state.IsValid()};
 356              Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
 357          }
 358  
 359          node.validation_signals->SyncWithValidationInterfaceQueue();
 360  
 361          CheckMempoolEphemeralInvariants(tx_pool);
 362      }
 363  
 364      node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
 365  
 366      WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
 367  }
 368  
 369  
 370  FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
 371  {
 372      SeedRandomStateForTest(SeedRand::ZEROS);
 373      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 374      const auto& node = g_setup->m_node;
 375      auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
 376  
 377      MockTime(fuzzed_data_provider, chainstate);
 378  
 379      // All RBF-spendable outpoints outside of the unsubmitted package
 380      std::set<COutPoint> mempool_outpoints;
 381      std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;
 382      for (const auto& outpoint : g_outpoints_coinbase_init_mature) {
 383          Assert(mempool_outpoints.insert(outpoint).second);
 384          outpoints_value[outpoint] = 50 * COIN;
 385      }
 386  
 387      auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);
 388      node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);
 389  
 390      auto tx_pool_{MakeMempool(fuzzed_data_provider, node)};
 391      MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());
 392  
 393      chainstate.SetMempool(&tx_pool);
 394  
 395      LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 300)
 396      {
 397          Assert(!mempool_outpoints.empty());
 398  
 399          std::vector<CTransactionRef> txs;
 400  
 401          // Make packages of 1-to-26 transactions
 402          const auto num_txs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 26);
 403          std::set<COutPoint> package_outpoints;
 404          while (txs.size() < num_txs) {
 405              // Create transaction to add to the mempool
 406              txs.emplace_back([&] {
 407                  CMutableTransaction tx_mut;
 408                  tx_mut.version = fuzzed_data_provider.ConsumeBool() ? TRUC_VERSION : CTransaction::CURRENT_VERSION;
 409                  tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
 410                  // Last transaction in a package needs to be a child of parents to get further in validation
 411                  // so the last transaction to be generated(in a >1 package) must spend all package-made outputs
 412                  // Note that this test currently only spends package outputs in last transaction.
 413                  bool last_tx = num_txs > 1 && txs.size() == num_txs - 1;
 414                  const auto num_in = last_tx ? package_outpoints.size()  : fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size());
 415                  auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, mempool_outpoints.size() * 2);
 416  
 417                  auto& outpoints = last_tx ? package_outpoints : mempool_outpoints;
 418  
 419                  Assert(!outpoints.empty());
 420  
 421                  CAmount amount_in{0};
 422                  for (size_t i = 0; i < num_in; ++i) {
 423                      // Pop random outpoint. We erase them to avoid double-spending
 424                      // while in this loop, but later add them back (unless last_tx).
 425                      auto pop = outpoints.begin();
 426                      std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints.size() - 1));
 427                      const auto outpoint = *pop;
 428                      outpoints.erase(pop);
 429                      // no need to update or erase from outpoints_value
 430                      amount_in += outpoints_value.at(outpoint);
 431  
 432                      // Create input
 433                      const auto sequence = ConsumeSequence(fuzzed_data_provider);
 434                      const auto script_sig = CScript{};
 435                      const auto script_wit_stack = fuzzed_data_provider.ConsumeBool() ? P2WSH_EMPTY_TRUE_STACK : P2WSH_EMPTY_TWO_STACK;
 436  
 437                      CTxIn in;
 438                      in.prevout = outpoint;
 439                      in.nSequence = sequence;
 440                      in.scriptSig = script_sig;
 441                      in.scriptWitness.stack = script_wit_stack;
 442  
 443                      tx_mut.vin.push_back(in);
 444                  }
 445  
 446                  // Duplicate an input
 447                  bool dup_input = fuzzed_data_provider.ConsumeBool();
 448                  if (dup_input) {
 449                      tx_mut.vin.push_back(tx_mut.vin.back());
 450                  }
 451  
 452                  // Refer to a non-existent input
 453                  if (fuzzed_data_provider.ConsumeBool()) {
 454                      tx_mut.vin.emplace_back();
 455                  }
 456  
 457                  // Make a p2pk output to make sigops adjusted vsize to violate TRUC rules, potentially, which is never spent
 458                  if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool()) {
 459                      tx_mut.vout.emplace_back(1000, CScript() << std::vector<unsigned char>(33, 0x02) << OP_CHECKSIG);
 460                      // Don't add any other outputs.
 461                      num_out = 1;
 462                      amount_in -= 1000;
 463                  }
 464  
 465                  const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, amount_in);
 466                  const auto amount_out = (amount_in - amount_fee) / num_out;
 467                  for (int i = 0; i < num_out; ++i) {
 468                      tx_mut.vout.emplace_back(amount_out, P2WSH_EMPTY);
 469                  }
 470                  auto tx = MakeTransactionRef(tx_mut);
 471                  // Restore previously removed outpoints, except in-package outpoints
 472                  if (!last_tx) {
 473                      for (const auto& in : tx->vin) {
 474                          // It's a fake input, or a new input, or a duplicate
 475                          Assert(in == CTxIn() || outpoints.insert(in.prevout).second || dup_input);
 476                      }
 477                      // Cache the in-package outpoints being made
 478                      for (size_t i = 0; i < tx->vout.size(); ++i) {
 479                          package_outpoints.emplace(tx->GetHash(), i);
 480                      }
 481                  }
 482                  // We need newly-created values for the duration of this run
 483                  for (size_t i = 0; i < tx->vout.size(); ++i) {
 484                      outpoints_value[COutPoint(tx->GetHash(), i)] = tx->vout[i].nValue;
 485                  }
 486                  return tx;
 487              }());
 488          }
 489  
 490          if (fuzzed_data_provider.ConsumeBool()) {
 491              MockTime(fuzzed_data_provider, chainstate);
 492          }
 493          if (fuzzed_data_provider.ConsumeBool()) {
 494              tx_pool.RollingFeeUpdate();
 495          }
 496          if (fuzzed_data_provider.ConsumeBool()) {
 497              const auto& txid = fuzzed_data_provider.ConsumeBool() ?
 498                                     txs.back()->GetHash() :
 499                                     PickValue(fuzzed_data_provider, mempool_outpoints).hash;
 500              const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN);
 501              tx_pool.PrioritiseTransaction(txid, delta);
 502          }
 503  
 504          // Remember all added transactions
 505          std::set<CTransactionRef> added;
 506          auto txr = std::make_shared<TransactionsDelta>(added);
 507          node.validation_signals->RegisterSharedValidationInterface(txr);
 508  
 509          // When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
 510          // and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
 511          // (the package is a test accept and ATMP is a submission).
 512          auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
 513  
 514          // Exercise client_maxfeerate logic
 515          std::optional<CFeeRate> client_maxfeerate{};
 516          if (fuzzed_data_provider.ConsumeBool()) {
 517              client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
 518          }
 519  
 520          const auto result_package = WITH_LOCK(::cs_main,
 521                                      return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
 522  
 523          // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
 524          // can be a source of divergence.
 525          const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
 526                                     /*bypass_limits=*/false, /*test_accept=*/!single_submit));
 527          const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
 528  
 529          node.validation_signals->SyncWithValidationInterfaceQueue();
 530          node.validation_signals->UnregisterSharedValidationInterface(txr);
 531  
 532          // There is only 1 transaction in the package. We did a test-package-accept and a ATMP
 533          if (single_submit) {
 534              Assert(passed != added.empty());
 535              Assert(passed == res.m_state.IsValid());
 536              if (passed) {
 537                  Assert(added.size() == 1);
 538                  Assert(txs.back() == *added.begin());
 539              }
 540          } else if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
 541              // We don't know anything about the validity since transactions were randomly generated, so
 542              // just use result_package.m_state here. This makes the expect_valid check meaningless, but
 543              // we can still verify that the contents of m_tx_results are consistent with m_state.
 544              const bool expect_valid{result_package.m_state.IsValid()};
 545              Assert(!CheckPackageMempoolAcceptResult(txs, result_package, expect_valid, &tx_pool));
 546          } else {
 547              // This is empty if it fails early checks, or "full" if transactions are looked at deeper
 548              Assert(result_package.m_tx_results.size() == txs.size() || result_package.m_tx_results.empty());
 549          }
 550  
 551          CheckMempoolTRUCInvariants(tx_pool);
 552  
 553          // Dust checks only make sense when dust is enforced
 554          if (tx_pool.m_opts.require_standard) {
 555              CheckMempoolEphemeralInvariants(tx_pool);
 556          }
 557      }
 558  
 559      node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
 560  
 561      WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
 562  }
 563  } // namespace
 564