mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-02 01:36:13 +00:00
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 #33994
This commit is contained in:
parent
cb3473a680
commit
a11297a904
@ -123,13 +123,16 @@ public:
|
||||
/**
|
||||
* Construct a new block template.
|
||||
*
|
||||
* During node initialization, this will wait until the tip is connected.
|
||||
*
|
||||
* @param[in] options options for creating the block
|
||||
* @param[in] cooldown wait for tip to be connected and IBD to complete.
|
||||
* If the best header is ahead of the tip, wait for the
|
||||
* tip to catch up. It's recommended to disable this on
|
||||
* regtest and signets with only one miner, as these
|
||||
* could stall.
|
||||
* @retval BlockTemplate a block template.
|
||||
* @retval std::nullptr if the node is shut down.
|
||||
*/
|
||||
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}) = 0;
|
||||
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}, bool cooldown = true) = 0;
|
||||
|
||||
/**
|
||||
* Checks if a given block is valid.
|
||||
|
||||
@ -22,7 +22,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") {
|
||||
isInitialBlockDownload @1 (context :Proxy.Context) -> (result: Bool);
|
||||
getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool);
|
||||
waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64 = .maxDouble) -> (result: Common.BlockRef);
|
||||
createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions) -> (result: BlockTemplate);
|
||||
createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions, cooldown: Bool = true) -> (result: BlockTemplate);
|
||||
checkBlock @5 (context :Proxy.Context, block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool);
|
||||
}
|
||||
|
||||
|
||||
@ -953,7 +953,7 @@ public:
|
||||
return WaitTipChanged(chainman(), notifications(), current_tip, timeout);
|
||||
}
|
||||
|
||||
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
|
||||
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options, bool cooldown) override
|
||||
{
|
||||
// Reject too-small values instead of clamping so callers don't silently
|
||||
// end up mining with different options than requested. This matches the
|
||||
@ -966,7 +966,24 @@ public:
|
||||
}
|
||||
|
||||
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
|
||||
if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};
|
||||
std::optional<BlockRef> maybe_tip{waitTipChanged(uint256::ZERO, MillisecondsDouble::max())};
|
||||
|
||||
if (!maybe_tip) return {};
|
||||
|
||||
if (cooldown) {
|
||||
// Do not return a template during IBD, because it can have long
|
||||
// pauses and sometimes takes a while to get started. Although this
|
||||
// is useful in general, it's gated behind the cooldown argument,
|
||||
// because on regtest and single miner signets this would wait
|
||||
// forever if no block was mined in the past day.
|
||||
while (chainman().IsInitialBlockDownload()) {
|
||||
maybe_tip = waitTipChanged(maybe_tip->hash, MillisecondsDouble{1000});
|
||||
if (!maybe_tip) return {};
|
||||
}
|
||||
|
||||
// Also wait during the final catch-up moments after IBD.
|
||||
if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip)) return {};
|
||||
}
|
||||
|
||||
BlockAssembler::Options assemble_options{options};
|
||||
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
|
||||
|
||||
@ -455,6 +455,37 @@ std::optional<BlockRef> GetTip(ChainstateManager& chainman)
|
||||
return BlockRef{tip->GetBlockHash(), tip->nHeight};
|
||||
}
|
||||
|
||||
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip)
|
||||
{
|
||||
uint256 last_tip_hash{last_tip.hash};
|
||||
|
||||
while (const std::optional<int> remaining = chainman.BlocksAheadOfTip()) {
|
||||
const int cooldown_seconds = std::clamp(*remaining, 3, 20);
|
||||
const auto cooldown_deadline{MockableSteadyClock::now() + std::chrono::seconds{cooldown_seconds}};
|
||||
|
||||
{
|
||||
WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
|
||||
kernel_notifications.m_tip_block_cv.wait_until(lock, cooldown_deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
|
||||
const auto tip_block = kernel_notifications.TipBlock();
|
||||
return chainman.m_interrupt || (tip_block && *tip_block != last_tip_hash);
|
||||
});
|
||||
if (chainman.m_interrupt) return false;
|
||||
|
||||
// If the tip changed during the wait, extend the deadline
|
||||
const auto tip_block = kernel_notifications.TipBlock();
|
||||
if (tip_block && *tip_block != last_tip_hash) {
|
||||
last_tip_hash = *tip_block;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// No tip change and the cooldown window has expired.
|
||||
if (MockableSteadyClock::now() >= cooldown_deadline) break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout)
|
||||
{
|
||||
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
|
||||
|
||||
@ -162,6 +162,25 @@ std::optional<BlockRef> GetTip(ChainstateManager& chainman);
|
||||
* Returns the current tip, or nullopt if the node is shutting down. */
|
||||
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout);
|
||||
|
||||
/**
|
||||
* Wait while the best known header extends the current chain tip AND at least
|
||||
* one block is being added to the tip every 3 seconds. If the tip is
|
||||
* sufficiently far behind, allow up to 20 seconds for the next tip update.
|
||||
*
|
||||
* It’s not safe to keep waiting, because a malicious miner could announce a
|
||||
* header and delay revealing the block, causing all other miners using this
|
||||
* software to stall. At the same time, we need to balance between the default
|
||||
* waiting time being brief, but not ending the cooldown prematurely when a
|
||||
* random block is slow to download (or process).
|
||||
*
|
||||
* The cooldown only applies to createNewBlock(), which is typically called
|
||||
* once per connected client. Subsequent templates are provided by waitNext().
|
||||
*
|
||||
* @param last_tip tip at the start of the cooldown window.
|
||||
*
|
||||
* @returns false if interrupted.
|
||||
*/
|
||||
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip);
|
||||
} // namespace node
|
||||
|
||||
#endif // BITCOIN_NODE_MINER_H
|
||||
|
||||
@ -165,7 +165,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const
|
||||
{
|
||||
UniValue blockHashes(UniValue::VARR);
|
||||
while (nGenerate > 0 && !chainman.m_interrupt) {
|
||||
std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({ .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true }));
|
||||
std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({ .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true }, /*cooldown=*/false));
|
||||
CHECK_NONFATAL(block_template);
|
||||
|
||||
std::shared_ptr<const CBlock> block_out;
|
||||
@ -376,7 +376,7 @@ static RPCHelpMan generateblock()
|
||||
{
|
||||
LOCK(chainman.GetMutex());
|
||||
{
|
||||
std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true})};
|
||||
std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script, .include_dummy_extranonce = true}, /*cooldown=*/false)};
|
||||
CHECK_NONFATAL(block_template);
|
||||
|
||||
block = block_template->getBlock();
|
||||
@ -870,8 +870,11 @@ static RPCHelpMan getblocktemplate()
|
||||
CBlockIndex* pindexPrevNew = chainman.m_blockman.LookupBlockIndex(tip);
|
||||
time_start = GetTime();
|
||||
|
||||
// Create new block
|
||||
block_template = miner.createNewBlock({.include_dummy_extranonce = true});
|
||||
// Create new block. Opt-out of cooldown mechanism, because it would add
|
||||
// a delay to each getblocktemplate call. This differs from typical
|
||||
// long-lived IPC usage, where the overhead is paid only when creating
|
||||
// the initial template.
|
||||
block_template = miner.createNewBlock({.include_dummy_extranonce = true}, /*cooldown=*/false);
|
||||
CHECK_NONFATAL(block_template);
|
||||
|
||||
|
||||
|
||||
@ -122,7 +122,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
||||
BOOST_CHECK(tx_mempool.size() == 0);
|
||||
|
||||
// Block template should only have a coinbase when there's nothing in the mempool
|
||||
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
|
||||
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
CBlock block{block_template->getBlock()};
|
||||
BOOST_REQUIRE_EQUAL(block.vtx.size(), 1U);
|
||||
@ -166,7 +166,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
||||
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
|
||||
TryAddToMempool(tx_mempool, high_fee_tx);
|
||||
|
||||
block_template = mining->createNewBlock(options);
|
||||
block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
block = block_template->getBlock();
|
||||
BOOST_REQUIRE_EQUAL(block.vtx.size(), 4U);
|
||||
@ -253,7 +253,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
||||
tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse;
|
||||
Txid hashLowFeeTx2 = tx.GetHash();
|
||||
TryAddToMempool(tx_mempool, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
|
||||
block_template = mining->createNewBlock(options);
|
||||
block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
block = block_template->getBlock();
|
||||
|
||||
@ -268,7 +268,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
||||
tx.vin[0].prevout.n = 1;
|
||||
tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee
|
||||
TryAddToMempool(tx_mempool, entry.Fee(10000).FromTx(tx));
|
||||
block_template = mining->createNewBlock(options);
|
||||
block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
block = block_template->getBlock();
|
||||
BOOST_REQUIRE_EQUAL(block.vtx.size(), 9U);
|
||||
@ -342,7 +342,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
LOCK(tx_mempool.cs);
|
||||
|
||||
// Just to make sure we can still make simple blocks
|
||||
auto block_template{mining->createNewBlock(options)};
|
||||
auto block_template{mining->createNewBlock(options, /*cooldown=*/false)};
|
||||
BOOST_REQUIRE(block_template);
|
||||
CBlock block{block_template->getBlock()};
|
||||
|
||||
@ -358,7 +358,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
}
|
||||
assert(tx_mempool.mapTx.size() == 51);
|
||||
assert(legacy_sigops == 20001);
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-blk-sigops"));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-blk-sigops"));
|
||||
}
|
||||
|
||||
{
|
||||
@ -369,7 +369,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
assert(tx_mempool.mapTx.empty());
|
||||
|
||||
// Just to make sure we can still make simple blocks
|
||||
auto block_template{mining->createNewBlock(options)};
|
||||
auto block_template{mining->createNewBlock(options, /*cooldown=*/false)};
|
||||
BOOST_REQUIRE(block_template);
|
||||
CBlock block{block_template->getBlock()};
|
||||
|
||||
@ -384,7 +384,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
assert(tx_mempool.mapTx.size() == 51);
|
||||
assert(legacy_sigops == 20001);
|
||||
|
||||
BOOST_REQUIRE(mining->createNewBlock(options));
|
||||
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
|
||||
}
|
||||
|
||||
{
|
||||
@ -414,7 +414,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
BOOST_CHECK(tx_mempool.GetIter(hash).has_value());
|
||||
tx.vin[0].prevout.hash = hash;
|
||||
}
|
||||
BOOST_REQUIRE(mining->createNewBlock(options));
|
||||
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
|
||||
}
|
||||
|
||||
{
|
||||
@ -424,7 +424,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
// orphan in tx_mempool, template creation fails
|
||||
hash = tx.GetHash();
|
||||
TryAddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
|
||||
}
|
||||
|
||||
{
|
||||
@ -445,7 +445,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee
|
||||
hash = tx.GetHash();
|
||||
TryAddToMempool(tx_mempool, entry.Fee(HIGHERFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
BOOST_REQUIRE(mining->createNewBlock(options));
|
||||
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
|
||||
}
|
||||
|
||||
{
|
||||
@ -461,7 +461,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
// give it a fee so it'll get mined
|
||||
TryAddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
// Should throw bad-cb-multiple
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-cb-multiple"));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-cb-multiple"));
|
||||
}
|
||||
|
||||
{
|
||||
@ -478,7 +478,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
tx.vout[0].scriptPubKey = CScript() << OP_2;
|
||||
hash = tx.GetHash();
|
||||
TryAddToMempool(tx_mempool, entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
|
||||
}
|
||||
|
||||
{
|
||||
@ -498,7 +498,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
next->BuildSkip();
|
||||
m_node.chainman->ActiveChain().SetTip(*next);
|
||||
}
|
||||
BOOST_REQUIRE(mining->createNewBlock(options));
|
||||
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
|
||||
// Extend to a 210000-long block chain.
|
||||
while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) {
|
||||
CBlockIndex* prev = m_node.chainman->ActiveChain().Tip();
|
||||
@ -510,7 +510,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
next->BuildSkip();
|
||||
m_node.chainman->ActiveChain().SetTip(*next);
|
||||
}
|
||||
BOOST_REQUIRE(mining->createNewBlock(options));
|
||||
BOOST_REQUIRE(mining->createNewBlock(options, /*cooldown=*/false));
|
||||
|
||||
// invalid p2sh txn in tx_mempool, template creation fails
|
||||
tx.vin[0].prevout.hash = txFirst[0]->GetHash();
|
||||
@ -526,7 +526,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
tx.vout[0].nValue -= LOWFEE;
|
||||
hash = tx.GetHash();
|
||||
TryAddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options), std::runtime_error, HasReason("block-script-verify-flag-failed"));
|
||||
BOOST_CHECK_EXCEPTION(mining->createNewBlock(options, /*cooldown=*/false), std::runtime_error, HasReason("block-script-verify-flag-failed"));
|
||||
|
||||
// Delete the dummy blocks again.
|
||||
while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) {
|
||||
@ -632,7 +632,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
||||
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1;
|
||||
BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
|
||||
|
||||
auto block_template = mining->createNewBlock(options);
|
||||
auto block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
|
||||
// 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::
|
||||
m_node.chainman->ActiveChain().Tip()->nHeight++;
|
||||
SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1);
|
||||
|
||||
block_template = mining->createNewBlock(options);
|
||||
block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
block = block_template->getBlock();
|
||||
BOOST_CHECK_EQUAL(block.vtx.size(), 5U);
|
||||
@ -725,7 +725,7 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const
|
||||
Txid hashFreeGrandchild = tx.GetHash();
|
||||
TryAddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
|
||||
|
||||
auto block_template = mining->createNewBlock(options);
|
||||
auto block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
CBlock block{block_template->getBlock()};
|
||||
BOOST_REQUIRE_EQUAL(block.vtx.size(), 6U);
|
||||
@ -755,7 +755,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||
options.include_dummy_extranonce = true;
|
||||
|
||||
// Create and check a simple template
|
||||
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
|
||||
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
{
|
||||
CBlock block{block_template->getBlock()};
|
||||
@ -806,7 +806,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||
* set at the end of the previous loop.
|
||||
*/
|
||||
if (current_height % 2 == 0) {
|
||||
block_template = mining->createNewBlock(options);
|
||||
block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(MiningInterface)
|
||||
const int64_t genesis_time{WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->GetBlockTime())};
|
||||
SetMockTime(genesis_time + 3 * 60);
|
||||
|
||||
block_template = mining->createNewBlock(options);
|
||||
block_template = mining->createNewBlock(options, /*cooldown=*/false);
|
||||
BOOST_REQUIRE(block_template);
|
||||
|
||||
// The template should use the mocked system time
|
||||
|
||||
@ -6282,6 +6282,19 @@ void ChainstateManager::RecalculateBestHeader()
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> ChainstateManager::BlocksAheadOfTip() const
|
||||
{
|
||||
LOCK(::cs_main);
|
||||
const CBlockIndex* best_header{m_best_header};
|
||||
const CBlockIndex* tip{ActiveChain().Tip()};
|
||||
// Only consider headers that extend the active tip; ignore competing branches.
|
||||
if (best_header && tip && best_header->nChainWork > tip->nChainWork &&
|
||||
best_header->GetAncestor(tip->nHeight) == tip) {
|
||||
return best_header->nHeight - tip->nHeight;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ChainstateManager::ValidatedSnapshotCleanup(Chainstate& validated_cs, Chainstate& unvalidated_cs)
|
||||
{
|
||||
AssertLockHeld(::cs_main);
|
||||
|
||||
@ -1355,6 +1355,10 @@ public:
|
||||
//! header in our block-index not known to be invalid, recalculate it.
|
||||
void RecalculateBestHeader() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
|
||||
//! Returns how many blocks the best header is ahead of the current tip,
|
||||
//! or nullopt if the best header does not extend the tip.
|
||||
std::optional<int> BlocksAheadOfTip() const LOCKS_EXCLUDED(::cs_main);
|
||||
|
||||
CCheckQueue<CScriptCheck>& GetCheckQueue() { return m_script_check_queue; }
|
||||
|
||||
~ChainstateManager();
|
||||
|
||||
@ -4,18 +4,22 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test the IPC (multiprocess) Mining interface."""
|
||||
import asyncio
|
||||
import time
|
||||
from contextlib import AsyncExitStack
|
||||
from io import BytesIO
|
||||
import re
|
||||
from test_framework.blocktools import NULL_OUTPOINT
|
||||
from test_framework.messages import (
|
||||
MAX_BLOCK_WEIGHT,
|
||||
CBlockHeader,
|
||||
CTransaction,
|
||||
CTxIn,
|
||||
CTxOut,
|
||||
CTxInWitness,
|
||||
ser_uint256,
|
||||
COIN,
|
||||
from_hex,
|
||||
msg_headers,
|
||||
)
|
||||
from test_framework.script import (
|
||||
CScript,
|
||||
@ -24,9 +28,11 @@ from test_framework.script import (
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than_or_equal,
|
||||
assert_not_equal
|
||||
)
|
||||
from test_framework.wallet import MiniWallet
|
||||
from test_framework.p2p import P2PInterface
|
||||
from test_framework.ipc_util import (
|
||||
destroying,
|
||||
mining_create_block_template,
|
||||
@ -141,9 +147,26 @@ class IPCMiningTest(BitcoinTestFramework):
|
||||
|
||||
async def async_routine():
|
||||
ctx, mining = await self.make_mining_ctx()
|
||||
blockref = await mining.getTip(ctx)
|
||||
|
||||
async with AsyncExitStack() as stack:
|
||||
self.log.debug("createNewBlock() should wait if tip is still updating")
|
||||
self.disconnect_nodes(0, 1)
|
||||
node1_block_hash = self.generate(self.nodes[1], 1, sync_fun=self.no_op)[0]
|
||||
header = from_hex(CBlockHeader(), self.nodes[1].getblockheader(node1_block_hash, False))
|
||||
header_only_peer = self.nodes[0].add_p2p_connection(P2PInterface())
|
||||
header_only_peer.send_and_ping(msg_headers([header]))
|
||||
start = time.time()
|
||||
async with destroying((await mining.createNewBlock(ctx, self.default_block_create_options)).result, ctx):
|
||||
pass
|
||||
# Lower-bound only: a heavily loaded CI host might still exceed 0.9s
|
||||
# even without the cooldown, so this can miss regressions but avoids
|
||||
# spurious failures.
|
||||
assert_greater_than_or_equal(time.time() - start, 0.9)
|
||||
|
||||
header_only_peer.peer_disconnect()
|
||||
self.connect_nodes(0, 1)
|
||||
self.sync_all()
|
||||
|
||||
self.log.debug("Create a template")
|
||||
template = await mining_create_block_template(mining, stack, ctx, self.default_block_create_options)
|
||||
assert template is not None
|
||||
@ -152,8 +175,9 @@ class IPCMiningTest(BitcoinTestFramework):
|
||||
header = (await template.getBlockHeader(ctx)).result
|
||||
assert_equal(len(header), block_header_size)
|
||||
block = await mining_get_block(template, ctx)
|
||||
assert_equal(ser_uint256(block.hashPrevBlock), blockref.result.hash)
|
||||
assert len(block.vtx) >= 1
|
||||
current_tip = self.nodes[0].getbestblockhash()
|
||||
assert_equal(ser_uint256(block.hashPrevBlock), ser_uint256(int(current_tip, 16)))
|
||||
assert_greater_than_or_equal(len(block.vtx), 1)
|
||||
txfees = await template.getTxFees(ctx)
|
||||
assert_equal(len(txfees.result), 0)
|
||||
txsigops = await template.getTxSigops(ctx)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user