blockencodings_tests.cpp raw

   1  // Copyright (c) 2011-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <blockencodings.h>
   6  #include <chainparams.h>
   7  #include <consensus/merkle.h>
   8  #include <pow.h>
   9  #include <streams.h>
  10  #include <test/util/random.h>
  11  #include <test/util/txmempool.h>
  12  
  13  #include <test/util/common.h>
  14  #include <test/util/setup_common.h>
  15  
  16  #include <boost/test/unit_test.hpp>
  17  
  18  const std::vector<std::pair<Wtxid, CTransactionRef>> empty_extra_txn;
  19  
  20  BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegTestingSetup)
  21  
  22  static CMutableTransaction BuildTransactionTestCase() {
  23      CMutableTransaction tx;
  24      tx.vin.resize(1);
  25      tx.vin[0].scriptSig.resize(10);
  26      tx.vout.resize(1);
  27      tx.vout[0].nValue = 42;
  28      return tx;
  29  }
  30  
  31  static CBlock BuildBlockTestCase(FastRandomContext& ctx) {
  32      CBlock block;
  33      CMutableTransaction tx = BuildTransactionTestCase();
  34  
  35      block.vtx.resize(3);
  36      block.vtx[0] = MakeTransactionRef(tx);
  37      block.nVersion = 42;
  38      block.hashPrevBlock = ctx.rand256();
  39      block.nBits = 0x207fffff;
  40  
  41      tx.vin[0].prevout.hash = Txid::FromUint256(ctx.rand256());
  42      tx.vin[0].prevout.n = 0;
  43      block.vtx[1] = MakeTransactionRef(tx);
  44  
  45      tx.vin.resize(10);
  46      for (size_t i = 0; i < tx.vin.size(); i++) {
  47          tx.vin[i].prevout.hash = Txid::FromUint256(ctx.rand256());
  48          tx.vin[i].prevout.n = 0;
  49      }
  50      block.vtx[2] = MakeTransactionRef(tx);
  51  
  52      bool mutated;
  53      block.hashMerkleRoot = BlockMerkleRoot(block, &mutated);
  54      assert(!mutated);
  55      while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce;
  56      return block;
  57  }
  58  
  59  // Number of shared use_counts we expect for a tx we haven't touched
  60  // (block + mempool entry + our copy from the GetSharedTx call)
  61  constexpr long SHARED_TX_OFFSET{3};
  62  
  63  BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
  64  {
  65      CTxMemPool& pool = *Assert(m_node.mempool);
  66      TestMemPoolEntryHelper entry;
  67      auto rand_ctx(FastRandomContext(uint256{42}));
  68      CBlock block(BuildBlockTestCase(rand_ctx));
  69  
  70      LOCK2(cs_main, pool.cs);
  71      TryAddToMempool(pool, entry.FromTx(block.vtx[2]));
  72      BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
  73  
  74      // Do a simple ShortTxIDs RT
  75      {
  76          CBlockHeaderAndShortTxIDs shortIDs{block, rand_ctx.rand64()};
  77  
  78          DataStream stream{};
  79          stream << shortIDs;
  80  
  81          CBlockHeaderAndShortTxIDs shortIDs2;
  82          stream >> shortIDs2;
  83  
  84          PartiallyDownloadedBlock partialBlock(&pool);
  85          BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
  86          BOOST_CHECK( partialBlock.IsTxAvailable(0));
  87          BOOST_CHECK(!partialBlock.IsTxAvailable(1));
  88          BOOST_CHECK( partialBlock.IsTxAvailable(2));
  89  
  90          BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 1);
  91  
  92          size_t poolSize = pool.size();
  93          pool.removeRecursive(*block.vtx[2], MemPoolRemovalReason::REPLACED);
  94          BOOST_CHECK_EQUAL(pool.size(), poolSize - 1);
  95  
  96          CBlock block2;
  97          {
  98              PartiallyDownloadedBlock tmp = partialBlock;
  99              BOOST_CHECK(partialBlock.FillBlock(block2, {}, /*segwit_active=*/true) == READ_STATUS_INVALID); // No transactions
 100              partialBlock = tmp;
 101          }
 102  
 103          // Wrong transaction
 104          {
 105              PartiallyDownloadedBlock tmp = partialBlock;
 106              partialBlock.FillBlock(block2, {block.vtx[2]}, /*segwit_active=*/true); // Current implementation doesn't check txn here, but don't require that
 107              partialBlock = tmp;
 108          }
 109          bool mutated;
 110          BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated));
 111  
 112          CBlock block3;
 113          BOOST_CHECK(partialBlock.FillBlock(block3, {block.vtx[1]}, /*segwit_active=*/true) == READ_STATUS_OK);
 114          BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString());
 115          BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString());
 116          BOOST_CHECK(!mutated);
 117      }
 118  }
 119  
 120  class TestHeaderAndShortIDs {
 121      // Utility to encode custom CBlockHeaderAndShortTxIDs
 122  public:
 123      CBlockHeader header;
 124      uint64_t nonce;
 125      std::vector<uint64_t> shorttxids;
 126      std::vector<PrefilledTransaction> prefilledtxn;
 127  
 128      explicit TestHeaderAndShortIDs(const CBlockHeaderAndShortTxIDs& orig) {
 129          DataStream stream{};
 130          stream << orig;
 131          stream >> *this;
 132      }
 133      explicit TestHeaderAndShortIDs(const CBlock& block, FastRandomContext& ctx) :
 134          TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block, ctx.rand64()}) {}
 135  
 136      uint64_t GetShortID(const Wtxid& txhash) const {
 137          DataStream stream{};
 138          stream << *this;
 139          CBlockHeaderAndShortTxIDs base;
 140          stream >> base;
 141          return base.GetShortID(txhash);
 142      }
 143  
 144      SERIALIZE_METHODS(TestHeaderAndShortIDs, obj) { READWRITE(obj.header, obj.nonce, Using<VectorFormatter<CustomUintFormatter<CBlockHeaderAndShortTxIDs::SHORTTXIDS_LENGTH>>>(obj.shorttxids), obj.prefilledtxn); }
 145  };
 146  
 147  BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
 148  {
 149      CTxMemPool& pool = *Assert(m_node.mempool);
 150      TestMemPoolEntryHelper entry;
 151      auto rand_ctx(FastRandomContext(uint256{42}));
 152      CBlock block(BuildBlockTestCase(rand_ctx));
 153  
 154      LOCK2(cs_main, pool.cs);
 155      TryAddToMempool(pool, entry.FromTx(block.vtx[2]));
 156      BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
 157  
 158      Txid txhash;
 159  
 160      // Test with pre-forwarding tx 1, but not coinbase
 161      {
 162          TestHeaderAndShortIDs shortIDs(block, rand_ctx);
 163          shortIDs.prefilledtxn.resize(1);
 164          shortIDs.prefilledtxn[0] = {1, block.vtx[1]};
 165          shortIDs.shorttxids.resize(2);
 166          shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[0]->GetWitnessHash());
 167          shortIDs.shorttxids[1] = shortIDs.GetShortID(block.vtx[2]->GetWitnessHash());
 168  
 169          DataStream stream{};
 170          stream << shortIDs;
 171  
 172          CBlockHeaderAndShortTxIDs shortIDs2;
 173          stream >> shortIDs2;
 174  
 175          PartiallyDownloadedBlock partialBlock(&pool);
 176          BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
 177          BOOST_CHECK(!partialBlock.IsTxAvailable(0));
 178          BOOST_CHECK( partialBlock.IsTxAvailable(1));
 179          BOOST_CHECK( partialBlock.IsTxAvailable(2));
 180  
 181          BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 1); // +1 because of partialBlock
 182  
 183          CBlock block2;
 184          {
 185              PartiallyDownloadedBlock tmp = partialBlock;
 186              BOOST_CHECK(partialBlock.FillBlock(block2, {}, /*segwit_active=*/true) == READ_STATUS_INVALID); // No transactions
 187              partialBlock = tmp;
 188          }
 189  
 190          // Wrong transaction
 191          {
 192              PartiallyDownloadedBlock tmp = partialBlock;
 193              partialBlock.FillBlock(block2, {block.vtx[1]}, /*segwit_active=*/true); // Current implementation doesn't check txn here, but don't require that
 194              partialBlock = tmp;
 195          }
 196          BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 2); // +2 because of partialBlock and block2
 197          bool mutated;
 198          BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated));
 199  
 200          CBlock block3;
 201          PartiallyDownloadedBlock partialBlockCopy = partialBlock;
 202          BOOST_CHECK(partialBlock.FillBlock(block3, {block.vtx[0]}, /*segwit_active=*/true) == READ_STATUS_OK);
 203          BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString());
 204          BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString());
 205          BOOST_CHECK(!mutated);
 206  
 207          BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 3); // +2 because of partialBlock and block2 and block3
 208  
 209          txhash = block.vtx[2]->GetHash();
 210          block.vtx.clear();
 211          block2.vtx.clear();
 212          block3.vtx.clear();
 213          BOOST_CHECK_EQUAL(pool.get(txhash).use_count(), SHARED_TX_OFFSET + 1 - 1); // + 1 because of partialBlock; -1 because of block.
 214      }
 215      BOOST_CHECK_EQUAL(pool.get(txhash).use_count(), SHARED_TX_OFFSET - 1); // -1 because of block
 216  }
 217  
 218  BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
 219  {
 220      CTxMemPool& pool = *Assert(m_node.mempool);
 221      TestMemPoolEntryHelper entry;
 222      auto rand_ctx(FastRandomContext(uint256{42}));
 223      CBlock block(BuildBlockTestCase(rand_ctx));
 224  
 225      LOCK2(cs_main, pool.cs);
 226      TryAddToMempool(pool, entry.FromTx(block.vtx[1]));
 227      BOOST_CHECK_EQUAL(pool.get(block.vtx[1]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
 228  
 229      Txid txhash;
 230  
 231      // Test with pre-forwarding coinbase + tx 2 with tx 1 in mempool
 232      {
 233          TestHeaderAndShortIDs shortIDs(block, rand_ctx);
 234          shortIDs.prefilledtxn.resize(2);
 235          shortIDs.prefilledtxn[0] = {0, block.vtx[0]};
 236          shortIDs.prefilledtxn[1] = {1, block.vtx[2]}; // id == 1 as it is 1 after index 1
 237          shortIDs.shorttxids.resize(1);
 238          shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[1]->GetWitnessHash());
 239  
 240          DataStream stream{};
 241          stream << shortIDs;
 242  
 243          CBlockHeaderAndShortTxIDs shortIDs2;
 244          stream >> shortIDs2;
 245  
 246          PartiallyDownloadedBlock partialBlock(&pool);
 247          BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
 248          BOOST_CHECK( partialBlock.IsTxAvailable(0));
 249          BOOST_CHECK( partialBlock.IsTxAvailable(1));
 250          BOOST_CHECK( partialBlock.IsTxAvailable(2));
 251  
 252          BOOST_CHECK_EQUAL(pool.get(block.vtx[1]->GetHash()).use_count(), SHARED_TX_OFFSET + 1);
 253  
 254          CBlock block2;
 255          PartiallyDownloadedBlock partialBlockCopy = partialBlock;
 256          BOOST_CHECK(partialBlock.FillBlock(block2, {}, /*segwit_active=*/true) == READ_STATUS_OK);
 257          BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString());
 258          bool mutated;
 259          BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString());
 260          BOOST_CHECK(!mutated);
 261  
 262          txhash = block.vtx[1]->GetHash();
 263          block.vtx.clear();
 264          block2.vtx.clear();
 265          BOOST_CHECK_EQUAL(pool.get(txhash).use_count(), SHARED_TX_OFFSET + 1 - 1); // + 1 because of partialBlock; -1 because of block.
 266      }
 267      BOOST_CHECK_EQUAL(pool.get(txhash).use_count(), SHARED_TX_OFFSET - 1); // -1 because of block
 268  }
 269  
 270  BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
 271  {
 272      CTxMemPool& pool = *Assert(m_node.mempool);
 273      CMutableTransaction coinbase = BuildTransactionTestCase();
 274  
 275      CBlock block;
 276      auto rand_ctx(FastRandomContext(uint256{42}));
 277      block.vtx.resize(1);
 278      block.vtx[0] = MakeTransactionRef(std::move(coinbase));
 279      block.nVersion = 42;
 280      block.hashPrevBlock = rand_ctx.rand256();
 281      block.nBits = 0x207fffff;
 282  
 283      bool mutated;
 284      block.hashMerkleRoot = BlockMerkleRoot(block, &mutated);
 285      assert(!mutated);
 286      while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce;
 287  
 288      // Test simple header round-trip with only coinbase
 289      {
 290          CBlockHeaderAndShortTxIDs shortIDs{block, rand_ctx.rand64()};
 291  
 292          DataStream stream{};
 293          stream << shortIDs;
 294  
 295          CBlockHeaderAndShortTxIDs shortIDs2;
 296          stream >> shortIDs2;
 297  
 298          PartiallyDownloadedBlock partialBlock(&pool);
 299          BOOST_CHECK(partialBlock.InitData(shortIDs2, empty_extra_txn) == READ_STATUS_OK);
 300          BOOST_CHECK(partialBlock.IsTxAvailable(0));
 301  
 302          CBlock block2;
 303          std::vector<CTransactionRef> vtx_missing;
 304          BOOST_CHECK(partialBlock.FillBlock(block2, vtx_missing, /*segwit_active=*/true) == READ_STATUS_OK);
 305          BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString());
 306          BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString());
 307          BOOST_CHECK(!mutated);
 308      }
 309  }
 310  
 311  BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
 312      CTxMemPool& pool = *Assert(m_node.mempool);
 313      TestMemPoolEntryHelper entry;
 314      auto rand_ctx(FastRandomContext(uint256{42}));
 315  
 316      CMutableTransaction mtx = BuildTransactionTestCase();
 317      mtx.vin[0].prevout.hash = Txid::FromUint256(rand_ctx.rand256());
 318      mtx.vin[0].prevout.n = 0;
 319      const CTransactionRef non_block_tx = MakeTransactionRef(std::move(mtx));
 320  
 321      CBlock block(BuildBlockTestCase(rand_ctx));
 322      std::vector<std::pair<Wtxid, CTransactionRef>> extra_txn;
 323      extra_txn.resize(10);
 324  
 325      LOCK2(cs_main, pool.cs);
 326      TryAddToMempool(pool, entry.FromTx(block.vtx[2]));
 327      BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
 328      // Ensure the non_block_tx is actually not in the block
 329      for (const auto &block_tx : block.vtx) {
 330          BOOST_CHECK_NE(block_tx->GetHash(), non_block_tx->GetHash());
 331      }
 332      // Ensure block.vtx[1] is not in pool
 333      BOOST_CHECK_EQUAL(pool.get(block.vtx[1]->GetHash()), nullptr);
 334  
 335      {
 336          const CBlockHeaderAndShortTxIDs cmpctblock{block, rand_ctx.rand64()};
 337          PartiallyDownloadedBlock partial_block(&pool);
 338          PartiallyDownloadedBlock partial_block_with_extra(&pool);
 339  
 340          BOOST_CHECK(partial_block.InitData(cmpctblock, extra_txn) == READ_STATUS_OK);
 341          BOOST_CHECK( partial_block.IsTxAvailable(0));
 342          BOOST_CHECK(!partial_block.IsTxAvailable(1));
 343          BOOST_CHECK( partial_block.IsTxAvailable(2));
 344  
 345          // Add an unrelated tx to extra_txn:
 346          extra_txn[0] = {non_block_tx->GetWitnessHash(), non_block_tx};
 347          // and a tx from the block that's not in the mempool:
 348          extra_txn[1] = {block.vtx[1]->GetWitnessHash(), block.vtx[1]};
 349  
 350          BOOST_CHECK(partial_block_with_extra.InitData(cmpctblock, extra_txn) == READ_STATUS_OK);
 351          BOOST_CHECK(partial_block_with_extra.IsTxAvailable(0));
 352          // This transaction is now available via extra_txn:
 353          BOOST_CHECK(partial_block_with_extra.IsTxAvailable(1));
 354          BOOST_CHECK(partial_block_with_extra.IsTxAvailable(2));
 355      }
 356  }
 357  
 358  BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest) {
 359      BlockTransactionsRequest req1;
 360      req1.blockhash = m_rng.rand256();
 361      req1.indexes.resize(4);
 362      req1.indexes[0] = 0;
 363      req1.indexes[1] = 1;
 364      req1.indexes[2] = 3;
 365      req1.indexes[3] = 4;
 366  
 367      DataStream stream{};
 368      stream << req1;
 369  
 370      BlockTransactionsRequest req2;
 371      stream >> req2;
 372  
 373      BOOST_CHECK_EQUAL(req1.blockhash.ToString(), req2.blockhash.ToString());
 374      BOOST_CHECK_EQUAL(req1.indexes.size(), req2.indexes.size());
 375      BOOST_CHECK_EQUAL(req1.indexes[0], req2.indexes[0]);
 376      BOOST_CHECK_EQUAL(req1.indexes[1], req2.indexes[1]);
 377      BOOST_CHECK_EQUAL(req1.indexes[2], req2.indexes[2]);
 378      BOOST_CHECK_EQUAL(req1.indexes[3], req2.indexes[3]);
 379  }
 380  
 381  BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationMaxTest) {
 382      // Check that the highest legal index is decoded correctly
 383      BlockTransactionsRequest req0;
 384      req0.blockhash = m_rng.rand256();
 385      req0.indexes.resize(1);
 386      req0.indexes[0] = 0xffff;
 387      DataStream stream{};
 388      stream << req0;
 389  
 390      BlockTransactionsRequest req1;
 391      stream >> req1;
 392      BOOST_CHECK_EQUAL(req0.indexes.size(), req1.indexes.size());
 393      BOOST_CHECK_EQUAL(req0.indexes[0], req1.indexes[0]);
 394  }
 395  
 396  BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest) {
 397      // Any set of index deltas that starts with N values that sum to (0x10000 - N)
 398      // causes the edge-case overflow that was originally not checked for. Such
 399      // a request cannot be created by serializing a real BlockTransactionsRequest
 400      // due to the overflow, so here we'll serialize from raw deltas.
 401      BlockTransactionsRequest req0;
 402      req0.blockhash = m_rng.rand256();
 403      req0.indexes.resize(3);
 404      req0.indexes[0] = 0x7000;
 405      req0.indexes[1] = 0x10000 - 0x7000 - 2;
 406      req0.indexes[2] = 0;
 407      DataStream stream{};
 408      stream << req0.blockhash;
 409      WriteCompactSize(stream, req0.indexes.size());
 410      WriteCompactSize(stream, req0.indexes[0]);
 411      WriteCompactSize(stream, req0.indexes[1]);
 412      WriteCompactSize(stream, req0.indexes[2]);
 413  
 414      BlockTransactionsRequest req1;
 415      try {
 416          stream >> req1;
 417          // before patch: deserialize above succeeds and this check fails, demonstrating the overflow
 418          BOOST_CHECK(req1.indexes[1] < req1.indexes[2]);
 419          // this shouldn't be reachable before or after patch
 420          BOOST_CHECK(0);
 421      } catch(std::ios_base::failure &) {
 422          // deserialize should fail
 423          BOOST_CHECK(true); // Needed to suppress "Test case [...] did not check any assertions"
 424      }
 425  }
 426  
 427  BOOST_AUTO_TEST_SUITE_END()
 428