LCOV - code coverage report
Current view: top level - src/test - blockencodings_tests.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 250 252 99.2 %
Date: 2026-06-25 07:23:51 Functions: 65 65 100.0 %

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

Generated by: LCOV version 1.16