cryptonote_core: reject duplicate tx hashes before pool lookup
What changed, and why it matters
This change adds a safety check in Monero's core code that rejects a block if it contains the same transaction hash more than once, before looking those transactions up in the memory pool. Duplicate transaction hashes in a single block could cause inconsistent behavior or be exploited to manipulate how transactions are fetched and included. The fix is small and defensive, but the commit message does not describe a specific attack or security impact.
Treat as a low-to-moderate hardening fix. Review whether duplicate tx hashes are also rejected at block validation/acceptance boundaries, not just during serialization. Monitor for related disclosures or follow-up fixes.
Security signals we found
Defensive validation added for duplicate transaction hashes in a block
Prevents potential inconsistent state from duplicate tx hash lookups
No explicit security claim or CVE in commit message
Patch is narrow and does not show exploit path
Evidence from the diff
The patch modifies get_block_complete_entry() in src/cryptonote_core/cryptonote_core.cpp to construct an unordered_set from block.tx_hashes and throw if the set size differs from the vector size, indicating duplicates. This prevents duplicate tx hashes from reaching the transaction pool lookup and subsequent block serialization. The change is a pre-condition guard rather than a full protocol rule enforcement.
Changed components
src/cryptonote_core/cryptonote_core.cppget_block_complete_entry()block transaction hash handlingInspect captured patch +3 / −0
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 8681471..854583c 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -1270,6 +1270,9 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------
block_complete_entry get_block_complete_entry(block& b, tx_memory_pool &pool)
{
+ const std::unordered_set<crypto::hash> tx_hashes(b.tx_hashes.cbegin(), b.tx_hashes.cend());
+ CHECK_AND_ASSERT_THROW_MES(tx_hashes.size() == b.tx_hashes.size(), "Duplicate transaction hashes in block");
+
block_complete_entry bce;
bce.block = cryptonote::block_to_blob(b);
bce.block_weight = 0; // we can leave it to 0, those txes aren't pruned
Why this scored 57/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.