6aa5d8d blockencodings: fix extra transaction count:
Same here, the first test commit is meant to help us characterize the extent of the fix.
It's not immediately obvious, for example, if this line would have passed before the fix as well.
<details><summary>test: characterize extra transaction miscount</summary>
diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp
index e4200cace2..45ecfcc595 100644
--- a/src/test/blockencodings_tests.cpp
+++ b/src/test/blockencodings_tests.cpp
@@ -144,6 +144,13 @@ public:
SERIALIZE_METHODS(TestHeaderAndShortIDs, obj) { READWRITE(obj.header, obj.nonce, Using<VectorFormatter<CustomUintFormatter<CBlockHeaderAndShortTxIDs::SHORTTXIDS_LENGTH>>>(obj.shorttxids), obj.prefilledtxn); }
};
+struct TestPartiallyDownloadedBlock : PartiallyDownloadedBlock {
+ using PartiallyDownloadedBlock::PartiallyDownloadedBlock;
+
+ size_t GetMempoolCount() const { return mempool_count; }
+ size_t GetExtraCount() const { return extra_count; }
+};
+
BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
{
CTxMemPool& pool = *Assert(m_node.mempool);
@@ -319,6 +326,13 @@ BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
const CTransactionRef non_block_tx = MakeTransactionRef(std::move(mtx));
CBlock block(BuildBlockTestCase(rand_ctx));
+ // Leave one transaction missing so scanning doesn't stop before the collision.
+ mtx = BuildTransactionTestCase();
+ mtx.vin[0].prevout.hash = Txid::FromUint256(rand_ctx.rand256());
+ block.vtx.push_back(MakeTransactionRef(std::move(mtx)));
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce;
+
std::vector<std::pair<Wtxid, CTransactionRef>> extra_txn;
extra_txn.resize(10);
@@ -352,6 +366,34 @@ BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
// This transaction is now available via extra_txn:
BOOST_CHECK(partial_block_with_extra.IsTxAvailable(1));
BOOST_CHECK(partial_block_with_extra.IsTxAvailable(2));
+
+ // Simulate a mempool collision after finding an unrelated extra transaction.
+ extra_txn[2] = {block.vtx[2]->GetWitnessHash(), non_block_tx};
+ TestPartiallyDownloadedBlock partial_block_with_extra_collision{&pool};
+ BOOST_CHECK_EQUAL(partial_block_with_extra_collision.InitData(cmpctblock, extra_txn), READ_STATUS_OK);
+ BOOST_CHECK( partial_block_with_extra_collision.IsTxAvailable(1));
+ BOOST_CHECK(!partial_block_with_extra_collision.IsTxAvailable(2));
+ BOOST_CHECK_EQUAL(partial_block_with_extra_collision.GetMempoolCount(), 1);
+ BOOST_CHECK_EQUAL(partial_block_with_extra_collision.GetExtraCount(), 0); // TODO: This should be 1
+
+ // Now also collide the extra-sourced slot: both counters decrement exactly once.
+ extra_txn[3] = {block.vtx[1]->GetWitnessHash(), non_block_tx};
+ TestPartiallyDownloadedBlock partial_block_with_extra_source_collision{&pool};
+ BOOST_CHECK_EQUAL(partial_block_with_extra_source_collision.InitData(cmpctblock, extra_txn), READ_STATUS_OK);
+ BOOST_CHECK(!partial_block_with_extra_source_collision.IsTxAvailable(1));
+ BOOST_CHECK(!partial_block_with_extra_source_collision.IsTxAvailable(2));
+ BOOST_CHECK_EQUAL(partial_block_with_extra_source_collision.GetMempoolCount(), 0);
+ BOOST_CHECK_NE(partial_block_with_extra_source_collision.GetExtraCount(), 0); // TODO: This should be 0
+
+ // Collided slots are terminal: not even the genuine transactions refill them.
+ extra_txn[4] = {block.vtx[2]->GetWitnessHash(), block.vtx[2]};
+ extra_txn[5] = {block.vtx[1]->GetWitnessHash(), block.vtx[1]};
+ TestPartiallyDownloadedBlock partial_block_no_refill{&pool};
+ BOOST_CHECK_EQUAL(partial_block_no_refill.InitData(cmpctblock, extra_txn), READ_STATUS_OK);
+ BOOST_CHECK(!partial_block_no_refill.IsTxAvailable(1));
+ BOOST_CHECK(!partial_block_no_refill.IsTxAvailable(2));
+ BOOST_CHECK_EQUAL(partial_block_no_refill.GetMempoolCount(), 0);
+ BOOST_CHECK_NE(partial_block_no_refill.GetExtraCount(), 0); // TODO: This should be 0
}
}
</details>
In which case the critical fix commit would be minimal and would show the extend of its reach:
diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp
--- a/src/test/blockencodings_tests.cpp (revision fa4411f8f31e2ea4397e929e706e003e3ba4762f)
+++ b/src/test/blockencodings_tests.cpp (revision 1a6233f82bb6e8f88379360be5b6b94694a8fd04)
@@ -374,7 +374,7 @@
BOOST_CHECK( partial_block_with_extra_collision.IsTxAvailable(1));
BOOST_CHECK(!partial_block_with_extra_collision.IsTxAvailable(2));
BOOST_CHECK_EQUAL(partial_block_with_extra_collision.GetMempoolCount(), 1);
- BOOST_CHECK_EQUAL(partial_block_with_extra_collision.GetExtraCount(), 0); // TODO: This should be 1
+ BOOST_CHECK_EQUAL(partial_block_with_extra_collision.GetExtraCount(), 1);
// Now also collide the extra-sourced slot: both counters decrement exactly once.
extra_txn[3] = {block.vtx[1]->GetWitnessHash(), non_block_tx};
@@ -383,7 +383,7 @@
BOOST_CHECK(!partial_block_with_extra_source_collision.IsTxAvailable(1));
BOOST_CHECK(!partial_block_with_extra_source_collision.IsTxAvailable(2));
BOOST_CHECK_EQUAL(partial_block_with_extra_source_collision.GetMempoolCount(), 0);
- BOOST_CHECK_NE(partial_block_with_extra_source_collision.GetExtraCount(), 0); // TODO: This should be 0
+ BOOST_CHECK_EQUAL(partial_block_with_extra_source_collision.GetExtraCount(), 0);
// Collided slots are terminal: not even the genuine transactions refill them.
extra_txn[4] = {block.vtx[2]->GetWitnessHash(), block.vtx[2]};
@@ -393,7 +393,7 @@
BOOST_CHECK(!partial_block_no_refill.IsTxAvailable(1));
BOOST_CHECK(!partial_block_no_refill.IsTxAvailable(2));
BOOST_CHECK_EQUAL(partial_block_no_refill.GetMempoolCount(), 0);
- BOOST_CHECK_NE(partial_block_no_refill.GetExtraCount(), 0); // TODO: This should be 0
+ BOOST_CHECK_EQUAL(partial_block_no_refill.GetExtraCount(), 0);
}
}