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
3133namespace 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+
210288CQuorumManager::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
232327CQuorumManager::~CQuorumManager ()
233328{
234329 Stop ();
330+ if (m_chain_view_subscriber) {
331+ UnregisterValidationInterface (m_chain_view_subscriber.get ());
332+ }
235333}
236334
237335void CQuorumManager::Start ()
@@ -523,7 +621,8 @@ bool CQuorumManager::RequestQuorumData(CNode* pfrom, CConnman& connman, const CQ
523621
524622std::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 ();
0 commit comments