Merge cc1001f3bf17b31512c05fb359e09483a07fb2a3 into 5f4422d68dc3530c353af1f87499de1c864b60ad

This commit is contained in:
Sjors Provoost 2025-03-17 03:54:00 +01:00 committed by GitHub
commit a30a966dee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 102 additions and 40 deletions

View File

@ -89,20 +89,25 @@ public:
/** /**
* Waits for the connected tip to change. During node initialization, this will * Waits for the connected tip to change. During node initialization, this will
* wait until the tip is connected. * wait until the tip is connected (regardless of `timeout`).
* *
* @param[in] current_tip block hash of the current chain tip. Function waits * @param[in] current_tip block hash of the current chain tip. Function waits
* for the chain tip to differ from this. * for the chain tip to differ from this.
* @param[in] timeout how long to wait for a new tip * @param[in] timeout how long to wait for a new tip (default is forever)
* @returns Hash and height of the current chain tip after this call. *
* @retval BlockRef hash and height of the current chain tip after this call.
* @retval std::nullopt if the node is shut down.
*/ */
virtual BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0; virtual std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
/** /**
* Construct a new block template * 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] options options for creating the block
* @returns a block template * @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 = {}) = 0;

View File

@ -1070,24 +1070,41 @@ public:
return BlockRef{tip->GetBlockHash(), tip->nHeight}; return BlockRef{tip->GetBlockHash(), tip->nHeight};
} }
BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
{ {
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
if (timeout < 0ms) timeout = 0ms;
if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
auto deadline{std::chrono::steady_clock::now() + timeout};
{ {
WAIT_LOCK(notifications().m_tip_block_mutex, lock); WAIT_LOCK(notifications().m_tip_block_mutex, lock);
notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) { // For callers convenience, wait longer than the provided timeout
// We need to wait for m_tip_block to be set AND for the value // during startup for the tip to be non-null. That way this function
// to differ from the current_tip value. // always returns valid tip information when possible and only
return (notifications().TipBlock() && notifications().TipBlock() != current_tip) || chainman().m_interrupt; // returns null when shutting down, not when timing out.
notifications().m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return notifications().TipBlock() || chainman().m_interrupt;
});
if (chainman().m_interrupt) return {};
// At this point TipBlock is set, so continue to wait until it is
// different then `current_tip` provided by caller.
notifications().m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
return Assume(notifications().TipBlock()) != current_tip || chainman().m_interrupt;
}); });
} }
// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
LOCK(::cs_main); if (chainman().m_interrupt) return {};
return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};
// Must release m_tip_block_mutex before getTip() locks cs_main, to
// avoid deadlocks.
return getTip();
} }
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
{ {
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};
BlockAssembler::Options assemble_options{options}; BlockAssembler::Options assemble_options{options};
ApplyArgsManOptions(*Assert(m_node.args), assemble_options); ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node); return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);

View File

@ -64,6 +64,7 @@
using kernel::CCoinsStats; using kernel::CCoinsStats;
using kernel::CoinStatsHashType; using kernel::CoinStatsHashType;
using interfaces::BlockRef;
using interfaces::Mining; using interfaces::Mining;
using node::BlockManager; using node::BlockManager;
using node::NodeContext; using node::NodeContext;
@ -286,14 +287,17 @@ static RPCHelpMan waitfornewblock()
NodeContext& node = EnsureAnyNodeContext(request.context); NodeContext& node = EnsureAnyNodeContext(request.context);
Mining& miner = EnsureMining(node); Mining& miner = EnsureMining(node);
auto block{CHECK_NONFATAL(miner.getTip()).value()}; // Abort if RPC came out of warmup too early
if (IsRPCRunning()) { BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
block = timeout ? miner.waitTipChanged(block.hash, std::chrono::milliseconds(timeout)) : miner.waitTipChanged(block.hash); std::optional<BlockRef> block = timeout ? miner.waitTipChanged(current_block.hash, std::chrono::milliseconds(timeout)) :
} miner.waitTipChanged(current_block.hash);
// Return current block upon shutdown
if (block) current_block = *block;
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("hash", current_block.hash.GetHex());
ret.pushKV("height", block.height); ret.pushKV("height", current_block.height);
return ret; return ret;
}, },
}; };
@ -332,22 +336,28 @@ static RPCHelpMan waitforblock()
NodeContext& node = EnsureAnyNodeContext(request.context); NodeContext& node = EnsureAnyNodeContext(request.context);
Mining& miner = EnsureMining(node); Mining& miner = EnsureMining(node);
auto block{CHECK_NONFATAL(miner.getTip()).value()}; // Abort if RPC came out of warmup too early
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout}; const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
while (IsRPCRunning() && block.hash != hash) { while (current_block.hash != hash) {
std::optional<BlockRef> block;
if (timeout) { if (timeout) {
auto now{std::chrono::steady_clock::now()}; auto now{std::chrono::steady_clock::now()};
if (now >= deadline) break; if (now >= deadline) break;
const MillisecondsDouble remaining{deadline - now}; const MillisecondsDouble remaining{deadline - now};
block = miner.waitTipChanged(block.hash, remaining); block = miner.waitTipChanged(current_block.hash, remaining);
} else { } else {
block = miner.waitTipChanged(block.hash); block = miner.waitTipChanged(current_block.hash);
} }
// Return current block upon shutdown
if (!block) break;
current_block = *block;
} }
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("hash", current_block.hash.GetHex());
ret.pushKV("height", block.height); ret.pushKV("height", current_block.height);
return ret; return ret;
}, },
}; };
@ -387,23 +397,29 @@ static RPCHelpMan waitforblockheight()
NodeContext& node = EnsureAnyNodeContext(request.context); NodeContext& node = EnsureAnyNodeContext(request.context);
Mining& miner = EnsureMining(node); Mining& miner = EnsureMining(node);
auto block{CHECK_NONFATAL(miner.getTip()).value()}; // Abort if RPC came out of warmup too early
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout}; const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
while (IsRPCRunning() && block.height < height) { while (current_block.height < height) {
std::optional<BlockRef> block;
if (timeout) { if (timeout) {
auto now{std::chrono::steady_clock::now()}; auto now{std::chrono::steady_clock::now()};
if (now >= deadline) break; if (now >= deadline) break;
const MillisecondsDouble remaining{deadline - now}; const MillisecondsDouble remaining{deadline - now};
block = miner.waitTipChanged(block.hash, remaining); block = miner.waitTipChanged(current_block.hash, remaining);
} else { } else {
block = miner.waitTipChanged(block.hash); block = miner.waitTipChanged(current_block.hash);
} }
// Return current block on shutdown
if (!block) break;
current_block = *block;
} }
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("hash", current_block.hash.GetHex());
ret.pushKV("height", block.height); ret.pushKV("height", current_block.height);
return ret; return ret;
}, },
}; };

View File

@ -46,6 +46,7 @@
#include <memory> #include <memory>
#include <stdint.h> #include <stdint.h>
using interfaces::BlockRef;
using interfaces::BlockTemplate; using interfaces::BlockTemplate;
using interfaces::Mining; using interfaces::Mining;
using node::BlockAssembler; using node::BlockAssembler;
@ -775,9 +776,22 @@ static RPCHelpMan getblocktemplate()
static unsigned int nTransactionsUpdatedLast; static unsigned int nTransactionsUpdatedLast;
const CTxMemPool& mempool = EnsureMemPool(node); const CTxMemPool& mempool = EnsureMemPool(node);
if (!lpval.isNull()) // Long Polling (BIP22)
{ if (!lpval.isNull()) {
// Wait to respond until either the best block changes, OR a minute has passed and there are more transactions /**
* Wait to respond until either the best block changes, OR there are more
* transactions.
*
* The check for new transactions first happens after 1 minute and
* subsequently every 10 seconds. BIP22 does not require this particular interval.
* On mainnet the mempool changes frequently enough that in practice this RPC
* returns after 60 seconds, or sooner if the best block changes.
*
* getblocktemplate is unlikely to be called by bitcoin-cli, so
* -rpcclienttimeout is not a concern. BIP22 recommends a long request timeout.
*
* The longpollid is assumed to be a tip hash if it has the right format.
*/
uint256 hashWatchedChain; uint256 hashWatchedChain;
unsigned int nTransactionsUpdatedLastLP; unsigned int nTransactionsUpdatedLastLP;
@ -786,6 +800,8 @@ static RPCHelpMan getblocktemplate()
// Format: <hashBestChain><nTransactionsUpdatedLast> // Format: <hashBestChain><nTransactionsUpdatedLast>
const std::string& lpstr = lpval.get_str(); const std::string& lpstr = lpval.get_str();
// Assume the longpollid is a block hash. If it's not then we return
// early below.
hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid"); hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid");
nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64)); nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64));
} }
@ -800,12 +816,20 @@ static RPCHelpMan getblocktemplate()
LEAVE_CRITICAL_SECTION(cs_main); LEAVE_CRITICAL_SECTION(cs_main);
{ {
MillisecondsDouble checktxtime{std::chrono::minutes(1)}; MillisecondsDouble checktxtime{std::chrono::minutes(1)};
while (tip == hashWatchedChain && IsRPCRunning()) { while (IsRPCRunning()) {
tip = miner.waitTipChanged(hashWatchedChain, checktxtime).hash; // If hashWatchedChain is not a real block hash, this will
// Timeout: Check transactions for update // return immediately.
// without holding the mempool lock to avoid deadlocks std::optional<BlockRef> maybe_tip{miner.waitTipChanged(hashWatchedChain, checktxtime)};
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) // Node is shutting down
if (!maybe_tip) break;
tip = maybe_tip->hash;
if (tip != hashWatchedChain) break;
// Check transactions for update without holding the mempool
// lock to avoid deadlocks.
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) {
break; break;
}
checktxtime = std::chrono::seconds(10); checktxtime = std::chrono::seconds(10);
} }
} }