optimization: cache PresaltedSipHasher in CBlockHeaderAndShortTxIDs

Replaces separate `shorttxidk0`/`shorttxidk1` members with a cached `PresaltedSipHasher`, so `GetShortID()` reuses the precomputed `SipHash` state instead of rebuilding it on every call.

`CBlockHeaderAndShortTxIDs` was never intended to be used before `FillShortTxIDSelector()` runs; doing so already relied on indeterminate salt values.
The new `Assert(m_hasher)` just makes this invariant explicit and fails fast if the object is used in an uninitialized state.
This commit is contained in:
Lőrinc 2025-09-30 19:03:02 -04:00
parent 9ca52a4cbe
commit 118d22ddb4
No known key found for this signature in database
GPG Key ID: 669FFF0FFA477A76
2 changed files with 16 additions and 13 deletions

View File

@ -17,11 +17,14 @@
#include <unordered_map>
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce) :
nonce(nonce),
shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) {
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce)
: nonce(nonce),
shorttxids(block.vtx.size() - 1),
prefilledtxn(1),
header(block)
{
FillShortTxIDSelector();
//TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
// TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
prefilledtxn[0] = {0, block.vtx[0]};
for (size_t i = 1; i < block.vtx.size(); i++) {
const CTransaction& tx = *block.vtx[i];
@ -29,21 +32,21 @@ CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const
}
}
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const {
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const
{
DataStream stream{};
stream << header << nonce;
CSHA256 hasher;
hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin());
uint256 shorttxidhash;
hasher.Finalize(shorttxidhash.begin());
shorttxidk0 = shorttxidhash.GetUint64(0);
shorttxidk1 = shorttxidhash.GetUint64(1);
m_hasher.emplace(shorttxidhash.GetUint64(0), shorttxidhash.GetUint64(1));
}
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const {
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const
{
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
PresaltedSipHasher hasher(shorttxidk0, shorttxidk1); // TODO extract
return hasher(wtxid.ToUint256()) & 0xffffffffffffL;
return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL;
}
/* Reconstructing a compact block is in the hot-path for block relay,

View File

@ -5,6 +5,7 @@
#ifndef BITCOIN_BLOCKENCODINGS_H
#define BITCOIN_BLOCKENCODINGS_H
#include <crypto/siphash.h>
#include <primitives/block.h>
#include <functional>
@ -87,8 +88,7 @@ typedef enum ReadStatus_t
} ReadStatus;
class CBlockHeaderAndShortTxIDs {
private:
mutable uint64_t shorttxidk0, shorttxidk1;
mutable std::optional<PresaltedSipHasher> m_hasher;
uint64_t nonce;
void FillShortTxIDSelector() const;
@ -112,7 +112,7 @@ public:
/**
* @param[in] nonce This should be randomly generated, and is used for the siphash secret key
*/
CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce);
CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce);
uint64_t GetShortID(const Wtxid& wtxid) const;