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()