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