Skip to content

Commit 6eac1d5

Browse files
committed
mining: add cooldown argument to createNewBlock()
At startup, if the needs to catch up, connected mining clients will receive a flood of new templates as new blocks are connected. Fix this by adding a cooldown argument to createNewBlock(). When set to true, block template creation is briefly paused while the best header chain is ahead of the tip. This wait only happens when the best header extends the current tip, to ignore competing branches. Additionally, cooldown waits for isInitialBlockDownload() to latch to false, which happens when there is less than a day of blocks left to sync. When cooldown is false createNewBlock() returns immediately. The argument is optional, because many tests are negatively impacted by this mechanism, and single miner signets could end up stuck if no block was mined for a day. The getblocktemplate RPC also opts out, because it would add a delay to each call. Fixes bitcoin#33994
1 parent 5a2f2e6 commit 6eac1d5

File tree

10 files changed

+149
-35
lines changed

10 files changed

+149
-35
lines changed

src/interfaces/mining.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,16 @@ class Mining
145145
/**
146146
* Construct a new block template.
147147
*
148-
* During node initialization, this will wait until the tip is connected.
149-
*
150148
* @param[in] options options for creating the block
149+
* @param[in] cooldown wait for tip to be connected and IBD to complete.
150+
* If the best header is ahead of the tip, wait for the
151+
* tip to catch up. It's recommended to disable this on
152+
* regtest and signets with only one miner, as these
153+
* could stall.
151154
* @retval BlockTemplate a block template.
152155
* @retval std::nullptr if the node is shut down.
153156
*/
154-
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}) = 0;
157+
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}, bool cooldown = true) = 0;
155158

156159
/**
157160
* Checks if a given block is valid.

src/ipc/capnp/mining.capnp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") {
1717
isInitialBlockDownload @1 (context :Proxy.Context) -> (result: Bool);
1818
getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool);
1919
waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef);
20-
createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions) -> (result: BlockTemplate);
20+
createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions, cooldown: Bool = true) -> (result: BlockTemplate);
2121
checkBlock @5 (context :Proxy.Context, block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool);
2222
}
2323

src/node/interfaces.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ class MinerImpl : public Mining
968968
return WaitTipChanged(chainman(), notifications(), current_tip, timeout);
969969
}
970970

971-
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
971+
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options, bool cooldown) override
972972
{
973973
// Reject too-small values instead of clamping so callers don't silently
974974
// end up mining with different options than requested. This matches the
@@ -981,7 +981,24 @@ class MinerImpl : public Mining
981981
}
982982

983983
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
984-
if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};
984+
std::optional<BlockRef> maybe_tip{waitTipChanged(uint256::ZERO, MillisecondsDouble::max())};
985+
986+
if (!maybe_tip) return {};
987+
988+
if (cooldown) {
989+
// Do not return a template during IBD, because it can have long
990+
// pauses and sometimes takes a while to get started. Although this
991+
// is useful in general, it's gated behind the cooldown argument,
992+
// because on regtest and single miner signets this would wait
993+
// forever if no block was mined in the past day.
994+
while (chainman().IsInitialBlockDownload()) {
995+
maybe_tip = waitTipChanged(maybe_tip->hash, MillisecondsDouble{1000});
996+
if (!maybe_tip) return {};
997+
}
998+
999+
// Also wait during the final catch-up moments after IBD.
1000+
if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip)) return {};
1001+
}
9851002

9861003
BlockAssembler::Options assemble_options{options};
9871004
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);

src/node/miner.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,37 @@ std::optional<BlockRef> GetTip(ChainstateManager& chainman)
455455
return BlockRef{tip->GetBlockHash(), tip->nHeight};
456456
}
457457

458+
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip)
459+
{
460+
uint256 last_tip_hash{last_tip.hash};
461+
462+
while (const std::optional<int> remaining = chainman.BlocksAheadOfTip()) {
463+
const int cooldown_seconds = std::clamp(*remaining, 3, 20);
464+
const auto cooldown_deadline{MockableSteadyClock::now() + std::chrono::seconds{cooldown_seconds}};
465+
466+
{
467+
WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
468+
kernel_notifications.m_tip_block_cv.wait_until(lock, cooldown_deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
469+
const auto tip_block = kernel_notifications.TipBlock();
470+
return chainman.m_interrupt || (tip_block && *tip_block != last_tip_hash);
471+
});
472+
if (chainman.m_interrupt) return false;
473+
474+
// If the tip changed during the wait, extend the deadline
475+
const auto tip_block = kernel_notifications.TipBlock();
476+
if (tip_block && *tip_block != last_tip_hash) {
477+
last_tip_hash = *tip_block;
478+
continue;
479+
}
480+
}
481+
482+
// No tip change and the cooldown window has expired.
483+
if (MockableSteadyClock::now() >= cooldown_deadline) break;
484+
}
485+
486+
return true;
487+
}
488+
458489
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout)
459490
{
460491
Assume(timeout >= 0ms); // No internal callers should use a negative timeout

src/node/miner.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,25 @@ std::optional<BlockRef> GetTip(ChainstateManager& chainman);
163163
* Returns the current tip, or nullopt if the node is shutting down. */
164164
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout);
165165

166+
/**
167+
* Wait while the best known header extends the current chain tip AND at least
168+
* one block is being added to the tip every 3 seconds. If the tip is
169+
* sufficiently far behind, allow up to 20 seconds for the next tip update.
170+
*
171+
* It’s not safe to keep waiting, because a malicious miner could announce a
172+
* header and delay revealing the block, causing all other miners using this
173+
* software to stall. At the same time, we need to balance between the default
174+
* waiting time being brief, but not ending the cooldown prematurely when a
175+
* random block is slow to download (or process).
176+
*
177+
* The cooldown only applies to createNewBlock(), which is typically called
178+
* once per connected client. Subsequent templates are provided by waitNext().
179+
*
180+
* @param last_tip tip at the start of the cooldown window.
181+
*
182+
* @returns false if interrupted.
183+
*/
184+
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip);
166185
} // namespace node
167186

168187
#endif // BITCOIN_NODE_MINER_H

src/rpc/mining.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const
165165
{
166166
UniValue blockHashes(UniValue::VARR);
167167
while (nGenerate > 0 && !chainman.m_interrupt) {
168-
std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({ .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true }));
168+
std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({ .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true }, /*cooldown=*/false));
169169
CHECK_NONFATAL(block_template);
170170

171171
std::shared_ptr<const CBlock> block_out;
@@ -376,7 +376,7 @@ static RPCHelpMan generateblock()
376376
{
377377
LOCK(chainman.GetMutex());
378378
{
379-
std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true})};
379+
std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true}, /*cooldown=*/false)};
380380
CHECK_NONFATAL(block_template);
381381

382382
block = block_template->getBlock();
@@ -870,8 +870,11 @@ static RPCHelpMan getblocktemplate()
870870
CBlockIndex* pindexPrevNew = chainman.m_blockman.LookupBlockIndex(tip);
871871
time_start = GetTime();
872872

873-
// Create new block
874-
block_template = miner.createNewBlock({.include_dummy_extranonce = true});
873+
// Create new block. Opt-out of cooldown mechanism, because it would add
874+
// a delay to each getblocktemplate call. This differs from typical
875+
// long-lived IPC usage, where the overhead is paid only when creating
876+
// the initial template.
877+
block_template = miner.createNewBlock({.include_dummy_extranonce = true}, /*cooldown=*/false);
875878
CHECK_NONFATAL(block_template);
876879

877880

src/test/miner_tests.cpp

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
122122
BOOST_CHECK(tx_mempool.size() == 0);
123123

124124
// Block template should only have a coinbase when there's nothing in the mempool
125-
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
125+
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options, /*cooldown=*/false);
126126
BOOST_REQUIRE(block_template);
127127
CBlock block{block_template->getBlock()};
128128
BOOST_REQUIRE_EQUAL(block.vtx.size(), 1U);
@@ -166,7 +166,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
166166
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
167167
TryAddToMempool(tx_mempool, high_fee_tx);
168168

169-
block_template = mining->createNewBlock(options);
169+
block_template = mining->createNewBlock(options, /*cooldown=*/false);
170170
BOOST_REQUIRE(block_template);
171171
block = block_template->getBlock();
172172
BOOST_REQUIRE_EQUAL(block.vtx.size(), 4U);
@@ -253,7 +253,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
253253
tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse;
254254
Txid hashLowFeeTx2 = tx.GetHash();
255255
TryAddToMempool(tx_mempool, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
256-
block_template = mining->createNewBlock(options);
256+
block_template = mining->createNewBlock(options, /*cooldown=*/false);
257257
BOOST_REQUIRE(block_template);
258258
block = block_template->getBlock();
259259

@@ -268,7 +268,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
268268
tx.vin[0].prevout.n = 1;
269269
tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee
270270
TryAddToMempool(tx_mempool, entry.Fee(10000).FromTx(tx));
271-
block_template = mining->createNewBlock(options);
271+
block_template = mining->createNewBlock(options, /*cooldown=*/false);
272272
BOOST_REQUIRE(block_template);
273273
block = block_template->getBlock();
274274
BOOST_REQUIRE_EQUAL(block.vtx.size(), 9U);
@@ -342,7 +342,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
342342
LOCK(tx_mempool.cs);
343343

344344
// Just to make sure we can still make simple blocks
345-
auto block_template{mining->createNewBlock(options)};
345+
auto block_template{mining->createNewBlock(options, /*cooldown=*/false)};
346346
BOOST_REQUIRE(block_template);
347347
CBlock block{block_template->getBlock()};
348348

@@ -358,7 +358,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
358358
}
359359
assert(tx_mempool.mapTx.size() == 51);
360360
assert(legacy_sigops == 20001);
361-
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-blk-sigops"));
361+
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-blk-sigops"));
362362
}
363363

364364
{
@@ -369,7 +369,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
369369
assert(tx_mempool.mapTx.empty());
370370

371371
// Just to make sure we can still make simple blocks
372-
auto block_template{mining->createNewBlock(options)};
372+
auto block_template{mining->createNewBlock(options, /*cooldown=*/false)};
373373
BOOST_REQUIRE(block_template);
374374
CBlock block{block_template->getBlock()};
375375

@@ -384,7 +384,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
384384
assert(tx_mempool.mapTx.size() == 51);
385385
assert(legacy_sigops == 20001);
386386

387-
BOOST_REQUIRE(mining->createNewBlock(options));
387+
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
388388
}
389389

390390
{
@@ -414,7 +414,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
414414
BOOST_CHECK(tx_mempool.GetIter(hash).has_value());
415415
tx.vin[0].prevout.hash = hash;
416416
}
417-
BOOST_REQUIRE(mining->createNewBlock(options));
417+
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
418418
}
419419

420420
{
@@ -424,7 +424,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
424424
// orphan in tx_mempool, template creation fails
425425
hash = tx.GetHash();
426426
TryAddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).FromTx(tx));
427-
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
427+
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
428428
}
429429

430430
{
@@ -445,7 +445,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
445445
tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee
446446
hash = tx.GetHash();
447447
TryAddToMempool(tx_mempool, entry.Fee(HIGHERFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
448-
BOOST_REQUIRE(mining->createNewBlock(options));
448+
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
449449
}
450450

451451
{
@@ -461,7 +461,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
461461
// give it a fee so it'll get mined
462462
TryAddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
463463
// Should throw bad-cb-multiple
464-
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-cb-multiple"));
464+
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-cb-multiple"));
465465
}
466466

467467
{
@@ -478,7 +478,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
478478
tx.vout[0].scriptPubKey = CScript() << OP_2;
479479
hash = tx.GetHash();
480480
TryAddToMempool(tx_mempool, entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
481-
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
481+
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
482482
}
483483

484484
{
@@ -498,7 +498,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
498498
next->BuildSkip();
499499
m_node.chainman->ActiveChain().SetTip(*next);
500500
}
501-
BOOST_REQUIRE(mining->createNewBlock(options));
501+
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
502502
// Extend to a 210000-long block chain.
503503
while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) {
504504
CBlockIndex* prev = m_node.chainman->ActiveChain().Tip();
@@ -510,7 +510,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
510510
next->BuildSkip();
511511
m_node.chainman->ActiveChain().SetTip(*next);
512512
}
513-
BOOST_REQUIRE(mining->createNewBlock(options));
513+
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
514514

515515
// invalid p2sh txn in tx_mempool, template creation fails
516516
tx.vin[0].prevout.hash = txFirst[0]->GetHash();
@@ -526,7 +526,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
526526
tx.vout[0].nValue -= LOWFEE;
527527
hash = tx.GetHash();
528528
TryAddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
529-
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("block-script-verify-flag-failed"));
529+
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("block-script-verify-flag-failed"));
530530

531531
// Delete the dummy blocks again.
532532
while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) {
@@ -632,7 +632,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
632632
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1;
633633
BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
634634

635-
auto block_template = mining->createNewBlock(options);
635+
auto block_template = mining->createNewBlock(options, /*cooldown=*/false);
636636
BOOST_REQUIRE(block_template);
637637

638638
// None of the of the absolute height/time locked tx should have made
@@ -649,7 +649,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
649649
m_node.chainman->ActiveChain().Tip()->nHeight++;
650650
SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1);
651651

652-
block_template = mining->createNewBlock(options);
652+
block_template = mining->createNewBlock(options, /*cooldown=*/false);
653653
BOOST_REQUIRE(block_template);
654654
block = block_template->getBlock();
655655
BOOST_CHECK_EQUAL(block.vtx.size(), 5U);
@@ -725,7 +725,7 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const
725725
Txid hashFreeGrandchild = tx.GetHash();
726726
TryAddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
727727

728-
auto block_template = mining->createNewBlock(options);
728+
auto block_template = mining->createNewBlock(options, /*cooldown=*/false);
729729
BOOST_REQUIRE(block_template);
730730
CBlock block{block_template->getBlock()};
731731
BOOST_REQUIRE_EQUAL(block.vtx.size(), 6U);
@@ -755,7 +755,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
755755
options.include_dummy_extranonce = true;
756756

757757
// Create and check a simple template
758-
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
758+
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options, /*cooldown=*/false);
759759
BOOST_REQUIRE(block_template);
760760
{
761761
CBlock block{block_template->getBlock()};
@@ -806,7 +806,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
806806
* set at the end of the previous loop.
807807
*/
808808
if (current_height % 2 == 0) {
809-
block_template = mining->createNewBlock(options);
809+
block_template = mining->createNewBlock(options, /*cooldown=*/false);
810810
BOOST_REQUIRE(block_template);
811811
}
812812

src/validation.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6299,6 +6299,19 @@ void ChainstateManager::RecalculateBestHeader()
62996299
}
63006300
}
63016301

6302+
std::optional<int> ChainstateManager::BlocksAheadOfTip() const
6303+
{
6304+
LOCK(::cs_main);
6305+
const CBlockIndex* best_header{m_best_header};
6306+
const CBlockIndex* tip{ActiveChain().Tip()};
6307+
// Only consider headers that extend the active tip; ignore competing branches.
6308+
if (best_header && tip && best_header->nChainWork > tip->nChainWork &&
6309+
best_header->GetAncestor(tip->nHeight) == tip) {
6310+
return best_header->nHeight - tip->nHeight;
6311+
}
6312+
return std::nullopt;
6313+
}
6314+
63026315
bool ChainstateManager::ValidatedSnapshotCleanup(Chainstate& validated_cs, Chainstate& unvalidated_cs)
63036316
{
63046317
AssertLockHeld(::cs_main);

src/validation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,10 @@ class ChainstateManager
13541354
//! header in our block-index not known to be invalid, recalculate it.
13551355
void RecalculateBestHeader() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
13561356

1357+
//! Returns how many blocks the best header is ahead of the current tip,
1358+
//! or nullopt if the best header does not extend the tip.
1359+
std::optional<int> BlocksAheadOfTip() const LOCKS_EXCLUDED(::cs_main);
1360+
13571361
CCheckQueue<CScriptCheck>& GetCheckQueue() { return m_script_check_queue; }
13581362

13591363
~ChainstateManager();

0 commit comments

Comments
 (0)