Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 4 additions & 64 deletions src/evo/cbtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,68 +46,8 @@ bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationSta
return true;
}

using QcHashMap = std::map<Consensus::LLMQType, std::vector<uint256>>;
using QcIndexedHashMap = std::map<Consensus::LLMQType, std::map<int16_t, uint256>>;

/**
* Handles the calculation or caching of qcHashes and qcIndexedHashes
* @param pindexPrev The const CBlockIndex* (ie a block) of a block. Both the Quorum list and quorum rotation activation status will be retrieved based on this block.
* @return nullopt if quorumCommitment was unable to be found, otherwise returns the qcHashes and qcIndexedHashes that were calculated or cached
*/
auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq::CQuorumBlockProcessor& quorum_block_processor) ->
std::optional<std::pair<QcHashMap /*qcHashes*/, QcIndexedHashMap /*qcIndexedHashes*/>> {
auto quorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(pindexPrev);

static Mutex cs_cache;
static std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> quorums_cached GUARDED_BY(cs_cache);
static std::map<Consensus::LLMQType, Uint256LruHashMap<std::pair<uint256, int>>> qc_hashes_cached GUARDED_BY(cs_cache);
static QcHashMap qcHashes_cached GUARDED_BY(cs_cache);
static QcIndexedHashMap qcIndexedHashes_cached GUARDED_BY(cs_cache);

LOCK(cs_cache);
if (quorums == quorums_cached) {
return std::make_pair(qcHashes_cached, qcIndexedHashes_cached);
}

// Quorums set is different, reset cached values
quorums_cached.clear();
qcHashes_cached.clear();
qcIndexedHashes_cached.clear();
if (qc_hashes_cached.empty()) {
llmq::utils::InitQuorumsCache(qc_hashes_cached, Params().GetConsensus());
}

for (const auto& [llmqType, vecBlockIndexes] : quorums) {
const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
assert(llmq_params_opt.has_value());
bool rotation_enabled = llmq::IsQuorumRotationEnabled(llmq_params_opt.value(), pindexPrev);
auto& vec_hashes = qcHashes_cached[llmqType];
vec_hashes.reserve(vecBlockIndexes.size());
auto& map_indexed_hashes = qcIndexedHashes_cached[llmqType];
for (const auto& blockIndex : vecBlockIndexes) {
uint256 block_hash{blockIndex->GetBlockHash()};

std::pair<uint256, int> qc_hash;
if (!qc_hashes_cached[llmqType].get(block_hash, qc_hash)) {
auto [pqc, dummy_hash] = quorum_block_processor.GetMinedCommitment(llmqType, block_hash);
if (dummy_hash == uint256::ZERO) {
// this should never happen
return std::nullopt;
}
qc_hash.first = ::SerializeHash(pqc);
qc_hash.second = rotation_enabled ? pqc.quorumIndex : 0;
qc_hashes_cached[llmqType].insert(block_hash, qc_hash);
}
if (rotation_enabled) {
map_indexed_hashes[qc_hash.second] = qc_hash.first;
} else {
vec_hashes.emplace_back(qc_hash.first);
}
}
}
std::swap(quorums_cached, quorums);
return std::make_pair(qcHashes_cached, qcIndexedHashes_cached);
}
using llmq::QcHashMap;
using llmq::QcIndexedHashMap;

auto CalcHashCountFromQCHashes(const QcHashMap& qcHashes)
{
Expand All @@ -124,15 +64,15 @@ bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPre

int64_t nTime1 = GetTimeMicros();

auto retVal = CachedGetQcHashesQcIndexedHashes(pindexPrev, quorum_block_processor);
auto retVal = quorum_block_processor.GetQcHashes(pindexPrev);
if (!retVal) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "commitment-not-found");
}
// The returned quorums are in reversed order, so the most recent one is at index 0
auto [qcHashes, qcIndexedHashes] = retVal.value();

int64_t nTime2 = GetTimeMicros(); nTimeMined += nTime2 - nTime1;
LogPrint(BCLog::BENCHMARK, " - CachedGetQcHashesQcIndexedHashes: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1), nTimeMined * 0.000001);
LogPrint(BCLog::BENCHMARK, " - GetQcHashes: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1), nTimeMined * 0.000001);

// now add the commitments from the current block, which are not returned by GetMinedAndActiveCommitmentsUntilBlock
// due to the use of pindexPrev (we don't have the tip index here)
Expand Down
70 changes: 70 additions & 0 deletions src/llmq/blockprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,11 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH
m_evoDb.Write(BuildInversedHeightKey(llmq_params.type, nHeight), pQuorumBaseBlockIndex->nHeight);
}

// Only once this commitment's state change is complete, so the caches can never be
// repopulated from a half-updated view. Callers all hold cs_main today, but the
// invalidation should not depend on that.
DropQcHashesCache();

{
LOCK(minableCommitmentsCs);
mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
Expand All @@ -380,6 +385,68 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH
return true;
}

void CQuorumBlockProcessor::DropQcHashesCache()
{
LOCK(m_qc_hashes_cache_mutex);
m_quorums_cached.clear();
m_qc_hashes_cached.clear();
m_qc_indexed_hashes_cached.clear();
// Clear per-type LRU contents but keep the map entries so InitQuorumsCache is not
// required on every subsequent miss.
for (auto& [_, cache] : m_qc_hashes_lru) {
cache.clear();
}
}

std::optional<std::pair<QcHashMap, QcIndexedHashMap>> CQuorumBlockProcessor::GetQcHashes(const CBlockIndex* pindexPrev) const
{
auto quorums = GetMinedAndActiveCommitmentsUntilBlock(pindexPrev);

LOCK(m_qc_hashes_cache_mutex);
if (quorums == m_quorums_cached) {
return std::make_pair(m_qc_hashes_cached, m_qc_indexed_hashes_cached);
}

// Quorums set is different, reset cached values
m_quorums_cached.clear();
m_qc_hashes_cached.clear();
m_qc_indexed_hashes_cached.clear();
if (m_qc_hashes_lru.empty()) {
utils::InitQuorumsCache(m_qc_hashes_lru, Params().GetConsensus());
}

for (const auto& [llmqType, vecBlockIndexes] : quorums) {
const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
assert(llmq_params_opt.has_value());
bool rotation_enabled = IsQuorumRotationEnabled(llmq_params_opt.value(), pindexPrev);
auto& vec_hashes = m_qc_hashes_cached[llmqType];
vec_hashes.reserve(vecBlockIndexes.size());
auto& map_indexed_hashes = m_qc_indexed_hashes_cached[llmqType];
for (const auto& blockIndex : vecBlockIndexes) {
uint256 block_hash{blockIndex->GetBlockHash()};

std::pair<uint256, int> qc_hash;
if (!m_qc_hashes_lru[llmqType].get(block_hash, qc_hash)) {
auto [pqc, dummy_hash] = GetMinedCommitment(llmqType, block_hash);
if (dummy_hash == uint256::ZERO) {
// this should never happen
return std::nullopt;
}
qc_hash.first = ::SerializeHash(pqc);
qc_hash.second = rotation_enabled ? pqc.quorumIndex : 0;
m_qc_hashes_lru[llmqType].insert(block_hash, qc_hash);
}
if (rotation_enabled) {
map_indexed_hashes[qc_hash.second] = qc_hash.first;
} else {
vec_hashes.emplace_back(qc_hash.first);
}
}
}
std::swap(m_quorums_cached, quorums);
return std::make_pair(m_qc_hashes_cached, m_qc_indexed_hashes_cached);
}

bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex)
{
AssertLockHeld(::cs_main);
Expand Down Expand Up @@ -408,6 +475,9 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, gsl::not_null<const C
m_evoDb.Erase(BuildInversedHeightKey(qc.llmqType, pindex->nHeight));
}

// Only once this commitment's state change is complete; see ProcessCommitment.
DropQcHashesCache();

WITH_LOCK(minableCommitmentsCs, mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash));

// if a reorg happened, we should allow to mine this commitment later
Expand Down
35 changes: 32 additions & 3 deletions src/llmq/blockprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ namespace llmq
class CFinalCommitment;
class CQuorumSnapshotManager;

//! Serialized hashes of the commitments mined for the active quorums, by LLMQ type.
using QcHashMap = std::map<Consensus::LLMQType, std::vector<uint256>>;
//! As above, but keyed by quorumIndex, for rotation-enabled types.
using QcIndexedHashMap = std::map<Consensus::LLMQType, std::map<int16_t, uint256>>;

class CQuorumBlockProcessor
{
private:
Expand All @@ -54,6 +59,18 @@ class CQuorumBlockProcessor

mutable std::map<Consensus::LLMQType, Uint256LruHashMap<bool>> mapHasMinedCommitmentCache GUARDED_BY(minableCommitmentsCs);

// Memoizes GetQcHashes(). The whole-result cache is keyed on the set of active
// quorum base blocks, the LRU on those base-block hashes; neither key identifies
// which CFinalCommitment was mined for a base, so both are dropped whenever mined
// commitment state changes (see DropQcHashesCache). Owning them here keeps that
// invalidation next to the writes it has to follow, and ties their lifetime to the
// block index whose CBlockIndex* the outer cache stores.
mutable Mutex m_qc_hashes_cache_mutex;
mutable std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> m_quorums_cached GUARDED_BY(m_qc_hashes_cache_mutex);
mutable std::map<Consensus::LLMQType, Uint256LruHashMap<std::pair<uint256, int>>> m_qc_hashes_lru GUARDED_BY(m_qc_hashes_cache_mutex);
mutable QcHashMap m_qc_hashes_cached GUARDED_BY(m_qc_hashes_cache_mutex);
mutable QcIndexedHashMap m_qc_indexed_hashes_cached GUARDED_BY(m_qc_hashes_cache_mutex);

public:
CQuorumBlockProcessor() = delete;
CQuorumBlockProcessor(const CQuorumBlockProcessor&) = delete;
Expand All @@ -66,9 +83,9 @@ class CQuorumBlockProcessor
EXCLUSIVE_LOCKS_REQUIRED(!minableCommitmentsCs);

bool ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, BlockValidationState& state,
bool fJustCheck, bool fBLSChecks) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs);
bool fJustCheck, bool fBLSChecks) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs, !m_qc_hashes_cache_mutex);
bool UndoBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs);
EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs, !m_qc_hashes_cache_mutex);

//! it returns hash of commitment if it should be relay, otherwise nullopt
std::optional<CInv> AddMineableCommitment(const CFinalCommitment& fqc) EXCLUSIVE_LOCKS_REQUIRED(!minableCommitmentsCs);
Expand All @@ -84,6 +101,15 @@ class CQuorumBlockProcessor
EXCLUSIVE_LOCKS_REQUIRED(!minableCommitmentsCs);
std::pair<CFinalCommitment, uint256> GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const;

/**
* Serialized hashes of the commitments mined for the quorums active as of pindexPrev.
*
* Memoized; returns nullopt if a commitment recorded as mined could not be read back,
* which should never happen.
*/
std::optional<std::pair<QcHashMap, QcIndexedHashMap>> GetQcHashes(const CBlockIndex* pindexPrev) const
EXCLUSIVE_LOCKS_REQUIRED(!m_qc_hashes_cache_mutex);

std::vector<const CBlockIndex*> GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null<const CBlockIndex*> pindex, size_t maxCount) const;
std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> GetMinedAndActiveCommitmentsUntilBlock(gsl::not_null<const CBlockIndex*> pindex) const;

Expand All @@ -93,9 +119,12 @@ class CQuorumBlockProcessor
size_t cycle) const;
std::optional<const CBlockIndex*> GetLastMinedCommitmentsByQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, int quorumIndex, size_t cycle) const;
private:
//! Called from every site that writes or erases mined commitment state.
void DropQcHashesCache() EXCLUSIVE_LOCKS_REQUIRED(!m_qc_hashes_cache_mutex);

static bool GetCommitmentsFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, std::multimap<Consensus::LLMQType, CFinalCommitment>& ret, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, BlockValidationState& state,
bool fJustCheck) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs);
bool fJustCheck) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs, !m_qc_hashes_cache_mutex);
size_t GetNumCommitmentsRequired(const Consensus::LLMQParams& llmqParams, int nHeight) const
EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !minableCommitmentsCs);
static uint256 GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight, int quorumIndex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
Expand Down
Loading