Skip to content

Commit 3db85e2

Browse files
feat: enhance CQuorumManager with active chain view and ancestor caching
- Introduced ActiveChainView structure to minimize cs_main contention. - Added ComputeAncestorCacheMaxSize method to determine cache size limits. - Implemented FindAncestorFast for efficient ancestor lookups. - Integrated ChainViewSubscriber for block tip updates and cache management. - Updated relevant methods to utilize the new caching mechanism for improved performance.
1 parent 368eebb commit 3db85e2

File tree

2 files changed

+162
-10
lines changed

2 files changed

+162
-10
lines changed

src/llmq/quorums.cpp

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
#include <util/time.h>
2626
#include <util/underlying.h>
2727
#include <validation.h>
28+
#include <validationinterface.h>
2829

30+
#include <algorithm>
2931
#include <cxxtimer.hpp>
3032

3133
namespace llmq
@@ -207,6 +209,82 @@ bool CQuorum::ReadContributions(const CDBWrapper& db)
207209
return true;
208210
}
209211

212+
size_t CQuorumManager::ComputeAncestorCacheMaxSize()
213+
{
214+
size_t max_size = 0;
215+
for (const auto& llmq : Params().GetConsensus().llmqs) {
216+
size_t size = llmq.max_cycles(llmq.keepOldConnections) * (llmq.dkgMiningWindowEnd - llmq.dkgMiningWindowStart) + 128;
217+
max_size = std::max(max_size, size);
218+
}
219+
// Clamp to reasonable bounds
220+
return std::clamp(max_size, size_t(512), size_t(4096));
221+
}
222+
223+
CQuorumManager::ActiveChainView CQuorumManager::GetActiveChainView() const
224+
{
225+
LOCK(cs_active_chain_view);
226+
return m_active_chain_view;
227+
}
228+
229+
const CBlockIndex* CQuorumManager::FindAncestorFast(const ActiveChainView& view, int target_height) const
230+
{
231+
if (target_height < 0 || target_height > view.height || !view.tip) {
232+
return nullptr;
233+
}
234+
LOCK(cs_ancestor_cache);
235+
if (m_lru_tip_hash != view.hash) {
236+
// Distinguish extension vs reorg (or skipped updates).
237+
// If the previous tip hash is an ancestor of the current tip at the recorded height,
238+
// this is an extension (possibly by multiple blocks) and the cache remains valid.
239+
// Otherwise, clear the cache to avoid cross-fork pollution.
240+
bool is_extension = false;
241+
if (m_lru_tip_height >= 0) {
242+
const CBlockIndex* old_tip_as_ancestor = view.tip->GetAncestor(m_lru_tip_height);
243+
if (old_tip_as_ancestor && old_tip_as_ancestor->GetBlockHash() == m_lru_tip_hash) {
244+
is_extension = true;
245+
}
246+
}
247+
if (!is_extension) {
248+
m_ancestor_lru.clear();
249+
}
250+
m_lru_tip_hash = view.hash;
251+
m_lru_tip_height = view.height;
252+
}
253+
const CBlockIndex* out;
254+
if (m_ancestor_lru.get(target_height, out)) {
255+
return out;
256+
}
257+
out = view.tip->GetAncestor(target_height);
258+
if (out) {
259+
m_ancestor_lru.insert(target_height, out);
260+
}
261+
return out;
262+
}
263+
264+
void CQuorumManager::ChainViewSubscriber::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
265+
{
266+
if (!pindexNew) return;
267+
268+
// Update snapshot
269+
{
270+
LOCK(m_qman.cs_active_chain_view);
271+
m_qman.m_active_chain_view.tip = pindexNew;
272+
m_qman.m_active_chain_view.height = pindexNew->nHeight;
273+
m_qman.m_active_chain_view.hash = pindexNew->GetBlockHash();
274+
}
275+
276+
// Handle ancestor cache: clear on reorg, keep on extension
277+
{
278+
LOCK(m_qman.cs_ancestor_cache);
279+
if (pindexFork != pindexNew->pprev) {
280+
// Reorg detected: clear cache
281+
m_qman.m_ancestor_lru.clear();
282+
}
283+
m_qman.m_lru_tip_hash = pindexNew->GetBlockHash();
284+
m_qman.m_lru_tip_height = pindexNew->nHeight;
285+
}
286+
}
287+
210288
CQuorumManager::CQuorumManager(CBLSWorker& _blsWorker, CChainState& chainstate, CDeterministicMNManager& dmnman,
211289
CDKGSessionManager& _dkgManager, CEvoDB& _evoDb,
212290
CQuorumBlockProcessor& _quorumBlockProcessor, CQuorumSnapshotManager& qsnapman,
@@ -222,16 +300,36 @@ CQuorumManager::CQuorumManager(CBLSWorker& _blsWorker, CChainState& chainstate,
222300
m_qsnapman{qsnapman},
223301
m_mn_activeman{mn_activeman},
224302
m_mn_sync{mn_sync},
225-
m_sporkman{sporkman}
303+
m_sporkman{sporkman},
304+
m_ancestor_lru{ComputeAncestorCacheMaxSize()}
226305
{
227306
utils::InitQuorumsCache(mapQuorumsCache, false);
228307
quorumThreadInterrupt.reset();
229308
MigrateOldQuorumDB(_evoDb);
309+
310+
// Initialize snapshot from current tip (under cs_main for bootstrap)
311+
{
312+
LOCK(::cs_main);
313+
const CBlockIndex* tip = m_chainstate.m_chain.Tip();
314+
if (tip) {
315+
LOCK(cs_active_chain_view);
316+
m_active_chain_view.tip = tip;
317+
m_active_chain_view.height = tip->nHeight;
318+
m_active_chain_view.hash = tip->GetBlockHash();
319+
}
320+
}
321+
322+
// Register ValidationInterface subscriber
323+
m_chain_view_subscriber = std::make_unique<ChainViewSubscriber>(*this);
324+
RegisterValidationInterface(m_chain_view_subscriber.get());
230325
}
231326

232327
CQuorumManager::~CQuorumManager()
233328
{
234329
Stop();
330+
if (m_chain_view_subscriber) {
331+
UnregisterValidationInterface(m_chain_view_subscriber.get());
332+
}
235333
}
236334

237335
void CQuorumManager::Start()
@@ -523,7 +621,8 @@ bool CQuorumManager::RequestQuorumData(CNode* pfrom, CConnman& connman, const CQ
523621

524622
std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const
525623
{
526-
const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip());
624+
auto view = GetActiveChainView();
625+
const CBlockIndex* pindex = view.tip ? view.tip : WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip());
527626
return ScanQuorums(llmqType, pindex, nCountRequested);
528627
}
529628

@@ -547,10 +646,16 @@ std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp
547646
// too early for this cycle, use the previous one
548647
// bail out if it's below genesis block
549648
if (quorumCycleMiningEndHeight < llmq_params_opt->dkgInterval) return {};
550-
pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight - llmq_params_opt->dkgInterval);
649+
auto view = GetActiveChainView();
650+
const CBlockIndex* ancestor = view.tip ? FindAncestorFast(view, quorumCycleMiningEndHeight - llmq_params_opt->dkgInterval) : pindexStart->GetAncestor(quorumCycleMiningEndHeight - llmq_params_opt->dkgInterval);
651+
if (!ancestor) return {};
652+
pindexStore = ancestor;
551653
} else if (pindexStart->nHeight > quorumCycleMiningEndHeight) {
552654
// we are past the mining phase of this cycle, use it
553-
pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight);
655+
auto view = GetActiveChainView();
656+
const CBlockIndex* ancestor = view.tip ? FindAncestorFast(view, quorumCycleMiningEndHeight) : pindexStart->GetAncestor(quorumCycleMiningEndHeight);
657+
if (!ancestor) return {};
658+
pindexStore = ancestor;
554659
}
555660
// everything else is inside the mining phase of this cycle, no pindexStore adjustment needed
556661

@@ -1210,7 +1315,21 @@ CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, con
12101315
size_t poolSize = llmq_params.signingActiveQuorumCount;
12111316

12121317
CBlockIndex* pindexStart;
1213-
{
1318+
auto view = qman.GetActiveChainView();
1319+
if (view.tip) {
1320+
if (signHeight == -1) {
1321+
signHeight = view.height;
1322+
}
1323+
int startBlockHeight = signHeight - signOffset;
1324+
if (startBlockHeight > view.height || startBlockHeight < 0) {
1325+
return {};
1326+
}
1327+
const CBlockIndex* ancestor = qman.FindAncestorFast(view, startBlockHeight);
1328+
if (!ancestor) {
1329+
return {};
1330+
}
1331+
pindexStart = const_cast<CBlockIndex*>(ancestor);
1332+
} else {
12141333
LOCK(::cs_main);
12151334
if (signHeight == -1) {
12161335
signHeight = active_chain.Height();

src/llmq/quorums.h

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
#include <saltedhasher.h>
1818
#include <util/threadinterrupt.h>
1919
#include <util/time.h>
20+
#include <validationinterface.h>
2021

2122
#include <gsl/pointers.h>
2223

2324
#include <atomic>
25+
#include <functional>
2426
#include <map>
2527
#include <utility>
2628

@@ -258,6 +260,33 @@ class CQuorumManager
258260
// it maps `quorum_hash` to `pindex`
259261
mutable Uint256LruHashMap<const CBlockIndex*, 128 /*max_size*/> quorumBaseBlockIndexCache;
260262

263+
// Active chain snapshot to avoid cs_main contention
264+
struct ActiveChainView {
265+
const CBlockIndex* tip{nullptr};
266+
int height{-1};
267+
uint256 hash;
268+
};
269+
mutable Mutex cs_active_chain_view;
270+
ActiveChainView m_active_chain_view GUARDED_BY(cs_active_chain_view);
271+
272+
// Ancestor lookup cache (height -> CBlockIndex*) for current tip
273+
mutable Mutex cs_ancestor_cache;
274+
mutable unordered_lru_cache<int, const CBlockIndex*, std::hash<int>> m_ancestor_lru GUARDED_BY(cs_ancestor_cache);
275+
mutable uint256 m_lru_tip_hash GUARDED_BY(cs_ancestor_cache);
276+
mutable int m_lru_tip_height GUARDED_BY(cs_ancestor_cache){-1};
277+
static size_t ComputeAncestorCacheMaxSize();
278+
279+
// ValidationInterface subscriber for tip updates
280+
class ChainViewSubscriber : public CValidationInterface {
281+
public:
282+
explicit ChainViewSubscriber(CQuorumManager& qman) : m_qman(qman) {}
283+
virtual ~ChainViewSubscriber() = default;
284+
void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override;
285+
private:
286+
CQuorumManager& m_qman;
287+
};
288+
std::unique_ptr<ChainViewSubscriber> m_chain_view_subscriber;
289+
261290
mutable ctpl::thread_pool workerPool;
262291
mutable CThreadInterrupt quorumThreadInterrupt;
263292

@@ -276,10 +305,10 @@ class CQuorumManager
276305
void Stop();
277306

278307
void TriggerQuorumDataRecoveryThreads(CConnman& connman, const CBlockIndex* pIndex) const
279-
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums);
308+
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums, !cs_active_chain_view, !cs_ancestor_cache);
280309

281310
void UpdatedBlockTip(const CBlockIndex* pindexNew, CConnman& connman, bool fInitialDownload) const
282-
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums);
311+
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums, !cs_active_chain_view, !cs_ancestor_cache);
283312

284313
[[nodiscard]] MessageProcessingResult ProcessMessage(CNode& pfrom, CConnman& connman, std::string_view msg_type,
285314
CDataStream& vRecv)
@@ -294,17 +323,21 @@ class CQuorumManager
294323
CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const
295324
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums);
296325
std::vector<CQuorumCPtr> ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const
297-
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums);
326+
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums, !cs_active_chain_view, !cs_ancestor_cache);
298327

299328
// this one is cs_main-free
300329
std::vector<CQuorumCPtr> ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart,
301330
size_t nCountRequested) const
302-
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums);
331+
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums, !cs_active_chain_view, !cs_ancestor_cache);
332+
333+
// Active chain snapshot accessors (public for use by SelectQuorumForSigning)
334+
ActiveChainView GetActiveChainView() const EXCLUSIVE_LOCKS_REQUIRED(!cs_active_chain_view);
335+
const CBlockIndex* FindAncestorFast(const ActiveChainView& view, int target_height) const EXCLUSIVE_LOCKS_REQUIRED(!cs_ancestor_cache);
303336

304337
private:
305338
// all private methods here are cs_main-free
306339
void CheckQuorumConnections(CConnman& connman, const Consensus::LLMQParams& llmqParams, const CBlockIndex* pindexNew) const
307-
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums);
340+
EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums, !cs_active_chain_view, !cs_ancestor_cache);
308341

309342
CQuorumPtr BuildQuorumFromCommitment(Consensus::LLMQType llmqType,
310343
gsl::not_null<const CBlockIndex*> pQuorumBaseBlockIndex,

0 commit comments

Comments
 (0)