/ src / test / blockencodings_tests.cpp
blockencodings_tests.cpp
  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()