cmpctblock.cpp raw

   1  // Copyright (c) 2026 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 <addrman.h>
   6  #include <blockencodings.h>
   7  #include <chain.h>
   8  #include <chainparams.h>
   9  #include <coins.h>
  10  #include <consensus/amount.h>
  11  #include <consensus/consensus.h>
  12  #include <consensus/merkle.h>
  13  #include <net.h>
  14  #include <net_processing.h>
  15  #include <netmessagemaker.h>
  16  #include <node/blockstorage.h>
  17  #include <node/mining_types.h>
  18  #include <policy/truc_policy.h>
  19  #include <primitives/block.h>
  20  #include <primitives/transaction.h>
  21  #include <protocol.h>
  22  #include <script/script.h>
  23  #include <serialize.h>
  24  #include <sync.h>
  25  #include <test/fuzz/FuzzedDataProvider.h>
  26  #include <test/fuzz/fuzz.h>
  27  #include <test/fuzz/util.h>
  28  #include <test/fuzz/util/net.h>
  29  #include <test/util/mining.h>
  30  #include <test/util/net.h>
  31  #include <test/util/random.h>
  32  #include <test/util/script.h>
  33  #include <test/util/setup_common.h>
  34  #include <test/util/time.h>
  35  #include <test/util/txmempool.h>
  36  #include <test/util/validation.h>
  37  #include <txmempool.h>
  38  #include <uint256.h>
  39  #include <util/check.h>
  40  #include <util/task_runner.h>
  41  #include <util/time.h>
  42  #include <util/translation.h>
  43  #include <validation.h>
  44  #include <validationinterface.h>
  45  
  46  #include <boost/multi_index/detail/hash_index_iterator.hpp>
  47  
  48  #include <cstddef>
  49  #include <cstdint>
  50  #include <functional>
  51  #include <iterator>
  52  #include <memory>
  53  #include <optional>
  54  #include <string>
  55  #include <thread>
  56  #include <utility>
  57  #include <vector>
  58  
  59  namespace {
  60  
  61  TestingSetup* g_setup;
  62  
  63  //! Fee each created tx will pay.
  64  const CAmount AMOUNT_FEE{1000};
  65  //! Cached coinbases that each iteration can copy and use.
  66  std::vector<std::pair<COutPoint, CAmount>> g_mature_coinbase;
  67  //! Constant value used to create valid headers.
  68  uint32_t g_nBits;
  69  //! One for each block the fuzzer generates.
  70  struct BlockInfo {
  71      std::shared_ptr<CBlock> block;
  72      uint256 hash;
  73      uint32_t height;
  74  };
  75  //! Used to access prefilledtxn and shorttxids.
  76  class FuzzedCBlockHeaderAndShortTxIDs : public CBlockHeaderAndShortTxIDs
  77  {
  78      using CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs;
  79  
  80  public:
  81      void AddPrefilledTx(PrefilledTransaction&& prefilledtx)
  82      {
  83          prefilledtxn.push_back(std::move(prefilledtx));
  84      }
  85  
  86      void RemoveCoinbasePrefill()
  87      {
  88          prefilledtxn.erase(prefilledtxn.begin());
  89      }
  90  
  91      void InsertCoinbaseShortTxID(uint64_t shorttxid)
  92      {
  93          shorttxids.insert(shorttxids.begin(), shorttxid);
  94      }
  95  
  96      void EraseShortTxIDs(size_t index)
  97      {
  98          shorttxids.erase(shorttxids.begin() + index);
  99      }
 100  
 101      size_t PrefilledTxCount() {
 102          return prefilledtxn.size();
 103      }
 104  
 105      size_t ShortTxIDCount() {
 106          return shorttxids.size();
 107      }
 108  };
 109  
 110  void ResetChainmanAndMempool(TestingSetup& setup)
 111  {
 112      SetMockTime(Params().GenesisBlock().Time());
 113  
 114      bilingual_str error{};
 115      setup.m_node.mempool.reset();
 116      setup.m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(setup.m_node), error);
 117      Assert(error.empty());
 118  
 119      setup.m_node.chainman.reset();
 120      setup.m_make_chainman();
 121      setup.LoadVerifyActivateChainstate();
 122  
 123      node::BlockCreateOptions options;
 124      options.coinbase_output_script = P2WSH_OP_TRUE;
 125  
 126      g_mature_coinbase.clear();
 127  
 128      for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
 129          COutPoint prevout{MineBlock(setup.m_node, options)};
 130          if (i < COINBASE_MATURITY) {
 131              LOCK(cs_main);
 132              CAmount subsidy{setup.m_node.chainman->ActiveChainstate().CoinsTip().GetCoin(prevout)->out.nValue};
 133              g_mature_coinbase.emplace_back(prevout, subsidy);
 134          }
 135      }
 136  }
 137  
 138  //! Used to run tasks in a std::thread to avoid DEBUG_LOCKORDER false positives.
 139  class ImmediateBackgroundTaskRunner : public util::TaskRunnerInterface
 140  {
 141  public:
 142      void insert(std::function<void()> func) override { std::thread(std::move(func)).join(); }
 143      void flush() override {}
 144      size_t size() override { return 0; }
 145  };
 146  
 147  } // namespace
 148  
 149  extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept;
 150  
 151  void initialize_cmpctblock()
 152  {
 153      static const auto testing_setup = MakeNoLogFileContext<TestingSetup>();
 154      g_setup = testing_setup.get();
 155      g_nBits = Params().GenesisBlock().nBits;
 156      // Replace validation_signals before creating chainman and mempool so they use it.
 157      testing_setup->m_node.validation_signals = std::make_unique<ValidationSignals>(std::make_unique<ImmediateBackgroundTaskRunner>());
 158      ResetChainmanAndMempool(*g_setup);
 159  }
 160  
 161  FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
 162  {
 163      SeedRandomStateForTest(SeedRand::ZEROS);
 164      FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
 165  
 166      FakeNodeClock clock{1610000000s};
 167      FakeSteadyClock steady_clock;
 168  
 169      auto setup = g_setup;
 170      auto& mempool = *setup->m_node.mempool;
 171      auto& chainman = static_cast<TestChainstateManager&>(*setup->m_node.chainman);
 172      chainman.ResetIbd();
 173      chainman.DisableNextWrite();
 174      const size_t initial_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
 175  
 176      AddrMan addrman{*setup->m_node.netgroupman, /*deterministic=*/true, /*consistency_check_ratio=*/0};
 177      auto& connman = *static_cast<ConnmanTestMsg*>(setup->m_node.connman.get());
 178      auto peerman = PeerManager::make(connman, addrman,
 179                                       /*banman=*/nullptr, chainman,
 180                                       mempool, *setup->m_node.warnings,
 181                                       PeerManager::Options{
 182                                           .deterministic_rng = true,
 183                                       });
 184      connman.SetMsgProc(peerman.get());
 185  
 186      setup->m_node.validation_signals->RegisterValidationInterface(peerman.get());
 187      setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
 188  
 189      LOCK(NetEventsInterface::g_msgproc_mutex);
 190  
 191      std::vector<CNode*> peers;
 192      for (int i = 0; i < 4; ++i) {
 193          peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock, i).release());
 194          CNode& p2p_node = *peers.back();
 195          FillNode(fuzzed_data_provider, connman, p2p_node);
 196          connman.AddTestNode(p2p_node);
 197      }
 198  
 199      // Stores blocks generated this iteration.
 200      std::vector<BlockInfo> info;
 201  
 202      // Coinbase UTXOs for this iteration.
 203      std::vector<std::pair<COutPoint, CAmount>> mature_coinbase = g_mature_coinbase;
 204  
 205      const uint64_t initial_sequence{WITH_LOCK(mempool.cs, return mempool.GetSequence())};
 206  
 207      auto create_tx = [&]() -> CTransactionRef {
 208          CMutableTransaction tx_mut;
 209          tx_mut.version = fuzzed_data_provider.ConsumeBool() ? CTransaction::CURRENT_VERSION : TRUC_VERSION;
 210          tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
 211  
 212          // Choose an outpoint from the mempool, created blocks, or coinbases.
 213          CAmount amount_in;
 214          COutPoint outpoint;
 215          unsigned long mempool_size = mempool.size();
 216          if (mempool_size != 0 && fuzzed_data_provider.ConsumeBool()) {
 217              size_t random_idx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, mempool_size - 1);
 218              CTransactionRef tx = WITH_LOCK(mempool.cs, return mempool.txns_randomized[random_idx].second->GetSharedTx(););
 219              outpoint = COutPoint(tx->GetHash(), 0);
 220              amount_in = tx->vout[0].nValue;
 221          } else if (info.size() != 0 && fuzzed_data_provider.ConsumeBool()) {
 222              // These blocks (and txs) may be invalid, use a spent output, or not be in the main chain.
 223              auto info_it = info.begin();
 224              std::advance(info_it, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info.size() - 1));
 225              auto tx_it = info_it->block->vtx.begin();
 226              std::advance(tx_it, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info_it->block->vtx.size() - 1));
 227              outpoint = COutPoint(tx_it->get()->GetHash(), 0);
 228              amount_in = tx_it->get()->vout[0].nValue;
 229          } else {
 230              auto coinbase_it = mature_coinbase.begin();
 231              std::advance(coinbase_it, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, mature_coinbase.size() - 1));
 232              outpoint = coinbase_it->first;
 233              amount_in = coinbase_it->second;
 234          }
 235  
 236          const auto sequence = ConsumeSequence(fuzzed_data_provider);
 237          const auto script_sig = CScript{};
 238          const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE};
 239  
 240          CTxIn in;
 241          in.prevout = outpoint;
 242          in.nSequence = sequence;
 243          in.scriptSig = script_sig;
 244          in.scriptWitness.stack = script_wit_stack;
 245          tx_mut.vin.push_back(in);
 246  
 247          const CAmount amount_out = amount_in - AMOUNT_FEE;
 248          tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE);
 249  
 250          auto tx = MakeTransactionRef(tx_mut);
 251          return tx;
 252      };
 253  
 254      auto create_block = [&]() {
 255          uint256 prev;
 256          uint32_t height;
 257  
 258          if (info.size() == 0 || fuzzed_data_provider.ConsumeBool()) {
 259              LOCK(cs_main);
 260              prev = chainman.ActiveChain().Tip()->GetBlockHash();
 261              height = chainman.ActiveChain().Height() + 1;
 262          } else {
 263              size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info.size() - 1);
 264              prev = info[index].hash;
 265              height = info[index].height + 1;
 266          }
 267  
 268          const auto new_time = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->GetMedianTimePast() + 1);
 269  
 270          CBlockHeader header;
 271          header.nNonce = 0;
 272          header.hashPrevBlock = prev;
 273          header.nBits = g_nBits;
 274          header.nTime = new_time;
 275          header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>();
 276  
 277          std::shared_ptr<CBlock> block = std::make_shared<CBlock>();
 278          *block = header;
 279  
 280          CMutableTransaction coinbase_tx;
 281          coinbase_tx.vin.resize(1);
 282          coinbase_tx.vin[0].prevout.SetNull();
 283          coinbase_tx.vin[0].scriptSig = CScript() << height << OP_0;
 284          coinbase_tx.vout.resize(1);
 285          coinbase_tx.vout[0].scriptPubKey = CScript() << OP_TRUE;
 286          coinbase_tx.vout[0].nValue = COIN;
 287          block->vtx.push_back(MakeTransactionRef(coinbase_tx));
 288  
 289          const auto mempool_size = mempool.size();
 290          if (fuzzed_data_provider.ConsumeBool() && mempool_size != 0) {
 291              // Add txns from the mempool. Since we do not include parents, it may be an invalid block.
 292              size_t num_txns = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, mempool_size);
 293              size_t random_idx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, mempool_size - 1);
 294  
 295              LOCK(mempool.cs);
 296              for (size_t i = random_idx; i < random_idx + num_txns; ++i) {
 297                  CTransactionRef mempool_tx = mempool.txns_randomized[i % mempool_size].second->GetSharedTx();
 298                  block->vtx.push_back(mempool_tx);
 299              }
 300          }
 301  
 302          // Create and add (possibly invalid) txns that are not in the mempool.
 303          if (fuzzed_data_provider.ConsumeBool()) {
 304              size_t new_txns = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 10);
 305              for (size_t i = 0; i < new_txns; ++i) {
 306                  CTransactionRef non_mempool_tx = create_tx();
 307                  block->vtx.push_back(non_mempool_tx);
 308              }
 309          }
 310  
 311          CBlockIndex* pindexPrev{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(prev))};
 312          chainman.GenerateCoinbaseCommitment(*block, pindexPrev);
 313  
 314          bool mutated;
 315          block->hashMerkleRoot = BlockMerkleRoot(*block, &mutated);
 316          FinalizeHeader(*block, chainman);
 317  
 318          BlockInfo block_info;
 319          block_info.block = block;
 320          block_info.hash = block->GetHash();
 321          block_info.height = height;
 322  
 323          return block_info;
 324      };
 325  
 326      LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000)
 327      {
 328          CSerializedNetMsg net_msg;
 329          bool sent_net_msg = true;
 330          bool requested_hb = false;
 331          bool sent_sendcmpct = false;
 332          bool valid_sendcmpct = false;
 333  
 334          CallOneOf(
 335              fuzzed_data_provider,
 336              [&]() {
 337                  // Send a compact block.
 338                  std::shared_ptr<CBlock> cblock;
 339  
 340                  // Pick an existing block or create a new block.
 341                  if (fuzzed_data_provider.ConsumeBool() && info.size() != 0) {
 342                      size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info.size() - 1);
 343                      cblock = info[index].block;
 344                  } else {
 345                      BlockInfo block_info = create_block();
 346                      cblock = block_info.block;
 347                      info.push_back(block_info);
 348                  }
 349  
 350                  uint64_t nonce = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
 351                  FuzzedCBlockHeaderAndShortTxIDs cmpctblock(*cblock, nonce);
 352  
 353                  if (fuzzed_data_provider.ConsumeBool()) {
 354                      CBlockHeaderAndShortTxIDs base_cmpctblock = cmpctblock;
 355                      net_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, base_cmpctblock);
 356                      return;
 357                  }
 358  
 359                  int prev_idx = 0;
 360                  size_t num_erased = 1;
 361                  size_t num_txs = cblock->vtx.size();
 362  
 363                  for (size_t i = 0; i < num_txs; ++i) {
 364                      if (i == 0) {
 365                          // Handle the coinbase specially. We either keep it prefilled or remove it.
 366                          if (fuzzed_data_provider.ConsumeBool()) continue;
 367  
 368                          // Remove the prefilled coinbase.
 369                          num_erased = 0;
 370                          uint64_t coinbase_shortid = cmpctblock.GetShortID(cblock->vtx[0]->GetWitnessHash());
 371                          cmpctblock.RemoveCoinbasePrefill();
 372                          cmpctblock.InsertCoinbaseShortTxID(coinbase_shortid);
 373                          continue;
 374                      }
 375  
 376                      if (fuzzed_data_provider.ConsumeBool()) continue;
 377  
 378                      uint16_t prefill_idx = num_erased == 0 ? i : i - prev_idx - 1;
 379                      prev_idx = i;
 380                      CTransactionRef txref = cblock->vtx[i];
 381                      PrefilledTransaction prefilledtx = {/*index=*/prefill_idx, txref};
 382                      cmpctblock.AddPrefilledTx(std::move(prefilledtx));
 383  
 384                      // Remove from shorttxids since we've prefilled. Subtract however many txs have been prefilled.
 385                      cmpctblock.EraseShortTxIDs(i - num_erased);
 386                      ++num_erased;
 387                  }
 388  
 389                  assert(cmpctblock.PrefilledTxCount() + cmpctblock.ShortTxIDCount() == num_txs);
 390  
 391                  CBlockHeaderAndShortTxIDs base_cmpctblock = cmpctblock;
 392                  net_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, base_cmpctblock);
 393              },
 394              [&]() {
 395                  // Send a blocktxn message for an existing block (if one exists).
 396                  size_t num_blocks = info.size();
 397                  if (num_blocks == 0) {
 398                      sent_net_msg = false;
 399                      return;
 400                  }
 401  
 402                  // Fetch an existing block and randomly choose transactions to send over.
 403                  size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_blocks - 1);
 404                  const BlockInfo& block_info = info[index];
 405                  BlockTransactions block_txn;
 406                  block_txn.blockhash = block_info.hash;
 407                  std::shared_ptr<CBlock> cblock = block_info.block;
 408  
 409                  for (size_t i = 0; i < cblock->vtx.size(); i++) {
 410                      if (fuzzed_data_provider.ConsumeBool()) continue;
 411  
 412                      block_txn.txn.push_back(cblock->vtx[i]);
 413                  }
 414  
 415                  net_msg = NetMsg::Make(NetMsgType::BLOCKTXN, block_txn);
 416              },
 417              [&]() {
 418                  // Send a headers message for an existing block (if one exists).
 419                  size_t num_blocks = info.size();
 420                  if (num_blocks == 0) {
 421                      sent_net_msg = false;
 422                      return;
 423                  }
 424  
 425                  // Choose an existing block and send a HEADERS message for it.
 426                  size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_blocks - 1);
 427                  CBlock block = *info[index].block;
 428                  block.vtx.clear(); // No tx in HEADERS.
 429                  std::vector<CBlock> headers;
 430                  headers.emplace_back(block);
 431  
 432                  net_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers));
 433              },
 434              [&]() {
 435                  // Send a sendcmpct message, optionally setting hb mode.
 436                  bool hb = fuzzed_data_provider.ConsumeBool();
 437                  uint64_t version{fuzzed_data_provider.ConsumeBool() ? CMPCTBLOCKS_VERSION : fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
 438                  net_msg = NetMsg::Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/hb, /*version=*/version);
 439                  requested_hb = hb;
 440                  sent_sendcmpct = true;
 441                  valid_sendcmpct = version == CMPCTBLOCKS_VERSION;
 442              },
 443              [&]() {
 444                  // Mine a block, but don't send it.
 445                  BlockInfo block_info = create_block();
 446                  info.push_back(block_info);
 447                  sent_net_msg = false;
 448              },
 449              [&]() {
 450                  // Send a transaction.
 451                  CTransactionRef tx = create_tx();
 452                  net_msg = NetMsg::Make(NetMsgType::TX, TX_WITH_WITNESS(*tx));
 453              },
 454              [&]() {
 455                  // Set mock time randomly or to tip's time.
 456                  if (fuzzed_data_provider.ConsumeBool()) {
 457                      clock.set(ConsumeTime(fuzzed_data_provider));
 458                  } else {
 459                      const NodeSeconds tip_time = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->Time());
 460                      clock.set(tip_time);
 461                  }
 462  
 463                  sent_net_msg = false;
 464              });
 465  
 466          if (!sent_net_msg) {
 467              continue;
 468          }
 469  
 470          CNode& random_node = *PickValue(fuzzed_data_provider, peers);
 471          connman.FlushSendBuffer(random_node);
 472          (void)connman.ReceiveMsgFrom(random_node, std::move(net_msg));
 473  
 474          bool more_work{true};
 475          while (more_work) {
 476              random_node.fPauseSend = false;
 477  
 478              more_work = connman.ProcessMessagesOnce(random_node);
 479              peerman->SendMessages(random_node);
 480          }
 481  
 482          std::vector<CNodeStats> stats;
 483          connman.GetNodeStats(stats);
 484  
 485          // We should have at maximum 3 HB peers.
 486          int num_hb = 0;
 487          for (const CNodeStats& stat : stats) {
 488              if (stat.m_bip152_highbandwidth_to) {
 489                  // HB peers cannot be feelers or other "special" connections (besides addr-fetch).
 490                  CNode* hb_peer = peers[stat.nodeid];
 491                  if (!hb_peer->fDisconnect) num_hb += 1;
 492                  assert(hb_peer->IsInboundConn() || hb_peer->IsOutboundOrBlockRelayConn() || hb_peer->IsManualConn() || hb_peer->IsAddrFetchConn());
 493              }
 494          }
 495          assert(num_hb <= 3);
 496  
 497          if (sent_sendcmpct && !random_node.fDisconnect) {
 498              // If the fuzzer sent SENDCMPCT with proper version, check the node's state matches what it sent.
 499              const CNodeStats& random_node_stats = stats[random_node.GetId()];
 500              if (valid_sendcmpct) assert(random_node_stats.m_bip152_highbandwidth_from == requested_hb);
 501          }
 502      }
 503  
 504      setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
 505      setup->m_node.validation_signals->UnregisterAllValidationInterfaces();
 506      connman.StopNodes();
 507  
 508      const size_t end_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
 509      const uint64_t end_sequence{WITH_LOCK(mempool.cs, return mempool.GetSequence())};
 510  
 511      if (initial_index_size != end_index_size || initial_sequence != end_sequence) {
 512          MakeRandDeterministicDANGEROUS(uint256::ZERO);
 513          ResetChainmanAndMempool(*g_setup);
 514      }
 515  }
 516