coins: use SipHash-1-3-UJ for CCoinsMap

Use the fixed-width `SipHasher13UJ::Hash` path for `CCoinsMap`, while keeping other `SaltedOutpointHasher` users on SipHash-2-4.
The salted outputs are process-local and must not be persisted, serialized, or compared across processes.

Retained cache entries identify real transaction outputs and therefore contain computed txids.
Missing-input validation may probe arbitrary claimed prevouts, but `FetchCoin()` erases each temporary entry immediately 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 content hash before activation.

Co-authored-by: Pieter Wuille <pieter@wuille.net>
This commit is contained in:
Lőrinc
2026-07-15 16:29:43 -07:00
parent 7ff55cc650
commit 2beab94896
5 changed files with 48 additions and 9 deletions

View File

@@ -8,6 +8,7 @@
#include <primitives/block.h>
#include <random.h>
#include <uint256.h>
#include <util/hasher.h>
#include <util/log.h>
#include <util/threadpool.h>
#include <util/trace.h>
@@ -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<Coin> 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

View File

@@ -9,6 +9,7 @@
#include <attributes.h>
#include <compressor.h>
#include <core_memusage.h>
#include <crypto/siphash.h>
#include <memusage.h>
#include <primitives/transaction.h>
#include <primitives/transaction_identifier.h>
@@ -18,7 +19,6 @@
#include <util/check.h>
#include <util/log.h>
#include <util/overflow.h>
#include <util/hasher.h>
#include <cassert>
#include <cstdint>
@@ -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<COutPoint,
CCoinsCacheEntry,
SaltedOutpointHasher,
SaltedCoinsCacheHasher,
std::equal_to<COutPoint>,
PoolAllocator<CoinsCachePair,
sizeof(CoinsCachePair) + sizeof(void*) * 4>>;

View File

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

View File

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

View File

@@ -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