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