diff --git a/src/coins.cpp b/src/coins.cpp index 72cbda2dd26..50e1aa05ab2 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,13 @@ TRACEPOINT_SEMAPHORE(utxocache, add); TRACEPOINT_SEMAPHORE(utxocache, spent); TRACEPOINT_SEMAPHORE(utxocache, uncache); +SaltedCoinsCacheHasher::SaltedCoinsCacheHasher(bool deterministic) + : m_hasher{ + deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64(), + deterministic ? 0xf4020d2e3983b0eb : FastRandomContext().rand64()} +{ +} + CoinsViewEmpty& CoinsViewEmpty::Get() { static CoinsViewEmpty instance; @@ -35,7 +43,7 @@ std::optional CCoinsViewCache::PeekCoin(const COutPoint& outpoint) const CCoinsViewCache::CCoinsViewCache(CCoinsView* in_base, bool deterministic) : CCoinsViewBacked(in_base), m_deterministic(deterministic), - cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource) + cacheCoins(0, SaltedCoinsCacheHasher{/*deterministic=*/deterministic}, CCoinsMap::key_equal{}, &m_cache_coins_memory_resource) { m_sentinel.second.SelfRef(m_sentinel); } @@ -331,7 +339,7 @@ void CCoinsViewCache::ReallocateCache() cacheCoins.~CCoinsMap(); m_cache_coins_memory_resource.~CCoinsMapMemoryResource(); ::new (&m_cache_coins_memory_resource) CCoinsMapMemoryResource{}; - ::new (&cacheCoins) CCoinsMap{0, SaltedOutpointHasher{/*deterministic=*/m_deterministic}, CCoinsMap::key_equal{}, &m_cache_coins_memory_resource}; + ::new (&cacheCoins) CCoinsMap{0, SaltedCoinsCacheHasher{/*deterministic=*/m_deterministic}, CCoinsMap::key_equal{}, &m_cache_coins_memory_resource}; } void CCoinsViewCache::SanityCheck() const diff --git a/src/coins.h b/src/coins.h index 7fc01af37ff..71dff015c3e 100644 --- a/src/coins.h +++ b/src/coins.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,6 @@ #include #include #include -#include #include #include @@ -219,6 +219,37 @@ public: } }; +/** + * SipHash-1-3-UJ based hasher for the coins cache and related coins containers. + * + * Retained entries identify real transaction outputs, so their keys contain computed txids. + * Missing-input lookups may contain arbitrary claimed prevouts, but FetchCoin() immediately + * erases their temporary entries when the backend lookup fails, so non-hash keys cannot + * accumulate. + * + * The assumeutxo loader assumes snapshot txids are valid while loading and verifies the + * complete snapshot's content hash before activation. + * + * Hash values are process-local and must not be persisted, serialized, or compared across + * processes. + * + * Having the hash noexcept lets libstdc++ recalculate it during rehash instead of storing it in + * each node. + */ +class SaltedCoinsCacheHasher +{ + const SipHasher13UJ m_hasher; + +public: + SaltedCoinsCacheHasher(bool deterministic = false); + + /** Hash an outpoint as its txid jumbo block followed by the zero-extended index as one normal block. */ + size_t operator()(const COutPoint& id) const noexcept + { + return m_hasher.Hash(id.hash.ToUint256(), uint64_t{id.n}); + } +}; + /** * PoolAllocator's MAX_BLOCK_SIZE_BYTES parameter here uses sizeof the data, and adds the size * of 4 pointers. We do not know the exact node size used in the std::unordered_node implementation @@ -229,7 +260,7 @@ public: */ using CCoinsMap = std::unordered_map, PoolAllocator>; diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 900d172d7c0..10c29ef7f73 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -245,7 +245,7 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co sentinel.second.SelfRef(sentinel); size_t dirty_count{0}; CCoinsMapMemoryResource resource; - CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource}; + CCoinsMap coins_map{0, SaltedCoinsCacheHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource}; LIMITED_WHILE (good_data && fuzzed_data_provider.ConsumeBool(), 10'000) { CCoinsCacheEntry coins_cache_entry; if (fuzzed_data_provider.ConsumeBool()) { diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index b12f7451b32..8e70b2a3e79 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -22,9 +22,9 @@ SaltedWtxidHasher::SaltedWtxidHasher() : m_hasher{ FastRandomContext().rand64()} {} -SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : m_hasher{ - deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64(), - deterministic ? 0xf4020d2e3983b0eb : FastRandomContext().rand64()} +SaltedOutpointHasher::SaltedOutpointHasher() : m_hasher{ + FastRandomContext().rand64(), + FastRandomContext().rand64()} {} SaltedSipHasher::SaltedSipHasher() : diff --git a/src/util/hasher.h b/src/util/hasher.h index 7e74c676769..b4488a93a46 100644 --- a/src/util/hasher.h +++ b/src/util/hasher.h @@ -58,7 +58,7 @@ class SaltedOutpointHasher const PresaltedSipHasher m_hasher; public: - SaltedOutpointHasher(bool deterministic = false); + SaltedOutpointHasher(); /** * Having the hash noexcept allows libstdc++'s unordered_map to recalculate