4747#include < util/check.h>
4848#include < util/fs.h>
4949#include < util/strencodings.h>
50+ #include < util/time.h>
5051#include < util/syserror.h>
5152#include < util/translation.h>
5253#include < validation.h>
@@ -120,6 +121,28 @@ static int ComputeNextBlockAndDepth(const CBlockIndex& tip, const CBlockIndex& b
120121 return &blockindex == &tip ? 1 : -1 ;
121122}
122123
124+ static bool IsHeadersOnlyMode (const JSONRPCRequest& request)
125+ {
126+ const NodeContext& node = EnsureAnyNodeContext (request.context );
127+ return node.args != nullptr && node.args ->GetBoolArg (" -headersonly" , DEFAULT_HEADERSONLY);
128+ }
129+
130+ static const CBlockIndex* GetRPCBestTip (const ChainstateManager& chainman, bool headers_only) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
131+ {
132+ if (headers_only && chainman.m_best_header != nullptr ) {
133+ return chainman.m_best_header ;
134+ }
135+ return chainman.ActiveChain ().Tip ();
136+ }
137+
138+ static bool HeaderTipIsInitialSync (const ChainstateManager& chainman, const CBlockIndex& header_tip)
139+ {
140+ const bool has_minimum_work{header_tip.nChainWork >= chainman.MinimumChainWork ()};
141+ const auto header_time{NodeSeconds{std::chrono::seconds{header_tip.GetBlockTime ()}}};
142+ const bool stale{header_time <= (NodeClock::now () - 24h)};
143+ return !has_minimum_work || stale;
144+ }
145+
123146static const CBlockIndex* ParseHashOrHeight (const UniValue& param, ChainstateManager& chainman)
124147{
125148 LOCK (::cs_main);
@@ -620,10 +643,11 @@ static RPCHelpMan getblockheader()
620643 const CBlockIndex* pblockindex;
621644 const CBlockIndex* tip;
622645 ChainstateManager& chainman = EnsureAnyChainman (request.context );
646+ const bool headers_only{IsHeadersOnlyMode (request)};
623647 {
624648 LOCK (cs_main);
625649 pblockindex = chainman.m_blockman .LookupBlockIndex (hash);
626- tip = chainman. ActiveChain (). Tip ( );
650+ tip = GetRPCBestTip (chainman, headers_only );
627651 }
628652
629653 if (!pblockindex) {
@@ -1375,24 +1399,26 @@ RPCHelpMan getblockchaininfo()
13751399 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
13761400{
13771401 ChainstateManager& chainman = EnsureAnyChainman (request.context );
1402+ const bool headers_only{IsHeadersOnlyMode (request)};
13781403 LOCK (cs_main);
13791404 Chainstate& active_chainstate = chainman.ActiveChainstate ();
13801405
1381- const CBlockIndex& tip{*CHECK_NONFATAL (active_chainstate.m_chain .Tip ())};
1382- const int height{tip.nHeight };
1406+ const CBlockIndex& active_tip{*CHECK_NONFATAL (active_chainstate.m_chain .Tip ())};
1407+ const CBlockIndex& rpc_tip{*CHECK_NONFATAL (GetRPCBestTip (chainman, headers_only))};
1408+ const int height{headers_only ? rpc_tip.nHeight : active_tip.nHeight };
13831409 UniValue obj (UniValue::VOBJ);
13841410 obj.pushKV (" chain" , chainman.GetParams ().GetChainTypeString ());
13851411 obj.pushKV (" blocks" , height);
13861412 obj.pushKV (" headers" , chainman.m_best_header ? chainman.m_best_header ->nHeight : -1 );
1387- obj.pushKV (" bestblockhash" , tip .GetBlockHash ().GetHex ());
1388- obj.pushKV (" bits" , strprintf (" %08x" , tip .nBits ));
1389- obj.pushKV (" target" , GetTarget (tip , chainman.GetConsensus ().powLimit ).GetHex ());
1390- obj.pushKV (" difficulty" , GetDifficulty (tip ));
1391- obj.pushKV (" time" , tip .GetBlockTime ());
1392- obj.pushKV (" mediantime" , tip .GetMedianTimePast ());
1393- obj.pushKV (" verificationprogress" , chainman.GuessVerificationProgress (&tip ));
1394- obj.pushKV (" initialblockdownload" , chainman.IsInitialBlockDownload ());
1395- obj.pushKV (" chainwork" , tip .nChainWork .GetHex ());
1413+ obj.pushKV (" bestblockhash" , rpc_tip .GetBlockHash ().GetHex ());
1414+ obj.pushKV (" bits" , strprintf (" %08x" , rpc_tip .nBits ));
1415+ obj.pushKV (" target" , GetTarget (rpc_tip , chainman.GetConsensus ().powLimit ).GetHex ());
1416+ obj.pushKV (" difficulty" , GetDifficulty (rpc_tip ));
1417+ obj.pushKV (" time" , rpc_tip .GetBlockTime ());
1418+ obj.pushKV (" mediantime" , rpc_tip .GetMedianTimePast ());
1419+ obj.pushKV (" verificationprogress" , chainman.GuessVerificationProgress (&rpc_tip ));
1420+ obj.pushKV (" initialblockdownload" , headers_only ? HeaderTipIsInitialSync (chainman, rpc_tip) : chainman.IsInitialBlockDownload ());
1421+ obj.pushKV (" chainwork" , rpc_tip .nChainWork .GetHex ());
13961422 obj.pushKV (" size_on_disk" , chainman.m_blockman .CalculateCurrentUsage ());
13971423 obj.pushKV (" pruned" , chainman.m_blockman .IsPruneMode ());
13981424 if (chainman.m_blockman .IsPruneMode ()) {
@@ -1544,35 +1570,56 @@ static RPCHelpMan getchaintips()
15441570 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
15451571{
15461572 ChainstateManager& chainman = EnsureAnyChainman (request.context );
1573+ const bool headers_only{IsHeadersOnlyMode (request)};
15471574 LOCK (cs_main);
15481575 CChain& active_chain = chainman.ActiveChain ();
1576+ const CBlockIndex* reference_tip{headers_only ? CHECK_NONFATAL (GetRPCBestTip (chainman, /* headers_only=*/ true )) : active_chain.Tip ()};
15491577
1550- /*
1551- * Idea: The set of chain tips is the active chain tip, plus orphan blocks which do not have another orphan building off of them.
1552- * Algorithm:
1553- * - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
1554- * - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
1555- * - Add the active chain tip
1556- */
15571578 std::set<const CBlockIndex*, CompareBlocksByHeight> setTips;
1558- std::set<const CBlockIndex*> setOrphans;
1559- std::set<const CBlockIndex*> setPrevs;
1579+ if (headers_only) {
1580+ /*
1581+ * In headers-only mode, use the set of all leaves in BlockIndex()
1582+ * relative to the selected header-chain reference tip.
1583+ */
1584+ std::set<const CBlockIndex*> setPrevs;
1585+
1586+ for (const auto & [_, block_index] : chainman.BlockIndex ()) {
1587+ if (block_index.pprev != nullptr ) {
1588+ setPrevs.insert (block_index.pprev );
1589+ }
1590+ }
15601591
1561- for (const auto & [_, block_index] : chainman.BlockIndex ()) {
1562- if (!active_chain. Contains (&block_index)) {
1563- setOrphans .insert (&block_index);
1564- setPrevs. insert (block_index. pprev );
1592+ for (const auto & [_, block_index] : chainman.BlockIndex ()) {
1593+ if (!setPrevs. contains (&block_index)) {
1594+ setTips .insert (&block_index);
1595+ }
15651596 }
1566- }
15671597
1568- for (std::set<const CBlockIndex*>::iterator it = setOrphans.begin (); it != setOrphans.end (); ++it) {
1569- if (setPrevs.erase (*it) == 0 ) {
1570- setTips.insert (*it);
1598+ // Always report the selected reference tip.
1599+ setTips.insert (reference_tip);
1600+ } else {
1601+ /*
1602+ * Preserve existing non-headersonly behavior:
1603+ * active tip + orphan leaves.
1604+ */
1605+ std::set<const CBlockIndex*> setOrphans;
1606+ std::set<const CBlockIndex*> setPrevs;
1607+
1608+ for (const auto & [_, block_index] : chainman.BlockIndex ()) {
1609+ if (!active_chain.Contains (&block_index)) {
1610+ setOrphans.insert (&block_index);
1611+ setPrevs.insert (block_index.pprev );
1612+ }
15711613 }
1572- }
15731614
1574- // Always report the currently active tip.
1575- setTips.insert (active_chain.Tip ());
1615+ for (std::set<const CBlockIndex*>::iterator it = setOrphans.begin (); it != setOrphans.end (); ++it) {
1616+ if (setPrevs.erase (*it) == 0 ) {
1617+ setTips.insert (*it);
1618+ }
1619+ }
1620+
1621+ setTips.insert (active_chain.Tip ());
1622+ }
15761623
15771624 /* Construct the output array. */
15781625 UniValue res (UniValue::VARR);
@@ -1581,13 +1628,22 @@ static RPCHelpMan getchaintips()
15811628 obj.pushKV (" height" , block->nHeight );
15821629 obj.pushKV (" hash" , block->phashBlock ->GetHex ());
15831630
1584- const int branchLen = block->nHeight - active_chain.FindFork (block)->nHeight ;
1631+ int branchLen{0 };
1632+ if (headers_only) {
1633+ const CBlockIndex* fork{LastCommonAncestor (block, reference_tip)};
1634+ branchLen = block->nHeight - (fork ? fork->nHeight : 0 );
1635+ } else {
1636+ branchLen = block->nHeight - active_chain.FindFork (block)->nHeight ;
1637+ }
15851638 obj.pushKV (" branchlen" , branchLen);
15861639
15871640 std::string status;
1588- if (active_chain.Contains (block)) {
1641+ if (!headers_only && active_chain.Contains (block)) {
15891642 // This block is part of the currently active chain.
15901643 status = " active" ;
1644+ } else if (headers_only && block == reference_tip) {
1645+ // This block is part of the selected header-chain reference tip.
1646+ status = " active" ;
15911647 } else if (block->nStatus & BLOCK_FAILED_MASK) {
15921648 // This block or one of its ancestors is invalid.
15931649 status = " invalid" ;
0 commit comments