diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp index 337462545f815e..777eb6566b942f 100644 --- a/src/test/orphanage_tests.cpp +++ b/src/test/orphanage_tests.cpp @@ -38,10 +38,10 @@ class TxOrphanageTest : public TxOrphanage } }; -static void MakeNewKeyWithFastRandomContext(CKey& key) +static void MakeNewKeyWithFastRandomContext(CKey& key, FastRandomContext& rand_ctx = g_insecure_rand_ctx) { std::vector keydata; - keydata = g_insecure_rand_ctx.randbytes(32); + keydata = rand_ctx.randbytes(32); key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true); assert(key.IsValid()); } @@ -66,6 +66,31 @@ static CTransactionRef MakeLargeOrphan() return MakeTransactionRef(tx); } +static CTransactionRef MakeTransactionSpending(const std::vector& outpoints, FastRandomContext& det_rand) +{ + CKey key; + MakeNewKeyWithFastRandomContext(key, det_rand); + CMutableTransaction tx; + for (const auto& outpoint : outpoints) { + tx.vin.emplace_back(CTxIn(outpoint)); + } + tx.vout.resize(1); + tx.vout[0].nValue = CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + return MakeTransactionRef(tx); +} + +// Make another (not necessarily valid) tx with the same txid but different wtxid. +static CTransactionRef MakeMutation(const CTransactionRef& ptx) +{ + CMutableTransaction tx(*ptx); + tx.vin[0].scriptWitness.stack.push_back({1}); + auto mutated_tx = MakeTransactionRef(tx); + assert(ptx->GetHash() == mutated_tx->GetHash()); + assert(ptx->GetWitnessHash() != mutated_tx->GetWitnessHash()); + return mutated_tx; +} + BOOST_AUTO_TEST_CASE(DoS_mapOrphans) { // This test had non-deterministic coverage due to @@ -178,6 +203,57 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) BOOST_CHECK_EQUAL(orphanage.CountOrphans(), expected_count); BOOST_CHECK_EQUAL(orphanage.TotalOrphanBytes(), expected_total_size); } + +BOOST_AUTO_TEST_CASE(process_block) +{ + FastRandomContext det_rand{true}; + TxOrphanageTest orphanage; + + // Create outpoints that will be spent by transactions in the block + std::vector outpoints; + const uint32_t num_outpoints{6}; + outpoints.reserve(num_outpoints); + for (uint32_t i{0}; i < num_outpoints; ++i) { + // All the hashes should be different, but change the n just in case. + outpoints.emplace_back(COutPoint{det_rand.rand256(), i}); + } + + CBlock block; + const NodeId node{0}; + + auto bo_tx_same_txid = MakeTransactionSpending({outpoints.at(0)}, det_rand); + BOOST_CHECK(orphanage.AddTx(bo_tx_same_txid, node, {})); + block.vtx.emplace_back(bo_tx_same_txid); + + // 2 transactions with the same txid but different witness + auto b_tx_same_txid_diff_witness = MakeTransactionSpending({outpoints.at(1)}, det_rand); + block.vtx.emplace_back(b_tx_same_txid_diff_witness); + + auto o_tx_same_txid_diff_witness = MakeMutation(b_tx_same_txid_diff_witness); + BOOST_CHECK(orphanage.AddTx(o_tx_same_txid_diff_witness, node, {})); + + // 2 different transactions that spend the same input. + auto b_tx_conflict = MakeTransactionSpending({outpoints.at(2)}, det_rand); + block.vtx.emplace_back(b_tx_conflict); + + auto o_tx_conflict = MakeTransactionSpending({outpoints.at(2)}, det_rand); + BOOST_CHECK(orphanage.AddTx(o_tx_conflict, node, {})); + + // 2 different transactions that have 1 overlapping input. + auto b_tx_conflict_partial = MakeTransactionSpending({outpoints.at(3), outpoints.at(4)}, det_rand); + block.vtx.emplace_back(b_tx_conflict_partial); + + auto o_tx_conflict_partial_2 = MakeTransactionSpending({outpoints.at(4), outpoints.at(5)}, det_rand); + BOOST_CHECK(orphanage.AddTx(o_tx_conflict_partial_2, node, {})); + + const auto removed = orphanage.EraseForBlock(block); + BOOST_CHECK_EQUAL(orphanage.Size(), 0); + for (const auto& expected_removed : {bo_tx_same_txid, o_tx_same_txid_diff_witness, o_tx_conflict, o_tx_conflict_partial_2}) { + const auto& expected_removed_wtxid = expected_removed->GetWitnessHash(); + BOOST_CHECK(std::find_if(removed.begin(), removed.end(), [&](const auto& wtxid) { return wtxid == expected_removed_wtxid; }) != removed.end()); + } +} + BOOST_AUTO_TEST_CASE(multiple_announcers) { const NodeId node0{0}; @@ -187,11 +263,15 @@ BOOST_AUTO_TEST_CASE(multiple_announcers) size_t expected_node0_size{0}; size_t expected_node1_size{0}; TxOrphanageTest orphanage; - // Check that accounting for bytes per peer is accurate. + + // Check accounting per peer. + // Check that EraseForPeer works with multiple announcers. { auto ptx{MakeLargeOrphan()}; const auto tx_size = ptx->GetTotalSize(); - orphanage.AddTx(ptx, node0, {}); + BOOST_CHECK(orphanage.AddTx(ptx, node0, {})); + BOOST_CHECK(orphanage.HaveTx(GenTxid::Txid(ptx->GetHash()))); + BOOST_CHECK(orphanage.HaveTx(GenTxid::Wtxid(ptx->GetWitnessHash()))); expected_total_size += tx_size; expected_total_count += 1; expected_node0_size += tx_size; @@ -199,19 +279,34 @@ BOOST_AUTO_TEST_CASE(multiple_announcers) BOOST_CHECK_EQUAL(orphanage.TotalOrphanBytes(), expected_total_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node0), expected_node0_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node1), expected_node1_size); + // Adding again should do nothing. - orphanage.AddTx(ptx, node0, {}); + BOOST_CHECK(!orphanage.AddTx(ptx, node0, {})); BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count); BOOST_CHECK_EQUAL(orphanage.TotalOrphanBytes(), expected_total_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node0), expected_node0_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node1), expected_node1_size); + + // Adding another tx with the same txid but different witness should not work. + auto ptx_mutated{MakeMutation(ptx)}; + BOOST_CHECK(!orphanage.AddTx(ptx_mutated, node0, {})); + BOOST_CHECK(!orphanage.HaveTx(GenTxid::Wtxid(ptx_mutated->GetWitnessHash()))); + + // It's too late to add parent_txids through AddTx. + BOOST_CHECK(!orphanage.AddTx(ptx, node0, {ptx->vin.at(0).prevout.hash})); + // Parent txids is empty because the tx exists but no parent_txids were provided. + BOOST_CHECK(orphanage.GetParentTxids(ptx->GetWitnessHash())->empty()); + // Parent txids is std::nullopt because the tx doesn't exist. + BOOST_CHECK(!orphanage.GetParentTxids(ptx_mutated->GetWitnessHash()).has_value()); + // Adding existing tx for another peer should change that peer's bytes, but not total bytes. - orphanage.AddTx(ptx, node1, {}); + BOOST_CHECK(!orphanage.AddTx(ptx, node1, {})); expected_node1_size += tx_size; BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count); BOOST_CHECK_EQUAL(orphanage.TotalOrphanBytes(), expected_total_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node0), expected_node0_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node1), expected_node1_size); + // if EraseForPeer is called for an orphan with multiple announcers, the orphanage should only // decrement the number of bytes for that peer. orphanage.EraseForPeer(node0); @@ -221,6 +316,7 @@ BOOST_AUTO_TEST_CASE(multiple_announcers) BOOST_CHECK_EQUAL(orphanage.TotalOrphanBytes(), expected_total_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node0), expected_node0_size); BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node1), expected_node1_size); + // EraseForPeer should delete the orphan if it's the only announcer left. orphanage.EraseForPeer(node1); expected_total_count -= 1; @@ -232,6 +328,13 @@ BOOST_AUTO_TEST_CASE(multiple_announcers) BOOST_CHECK_EQUAL(orphanage.BytesFromPeer(node1), expected_node1_size); BOOST_CHECK(!orphanage.HaveTx(GenTxid::Txid(ptx->GetHash()))); } + + // EraseOrphanOfPeer + + // Check that erasure for blocks, expiration erases for all peers. + { + + } } BOOST_AUTO_TEST_SUITE_END()