mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-13 11:35:20 +01:00
Merge 798bc8d400
into a50af6e4c4
This commit is contained in:
commit
1e26487749
6 changed files with 129 additions and 44 deletions
5
doc/release-30635.md
Normal file
5
doc/release-30635.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
Updated RPCs
|
||||
------------
|
||||
|
||||
- The waitfornewblock takes an optional `current_tip` argument. It is also no longer hidden. (#30635)
|
||||
- The waitforblock and waitforblockheight RPCs are no longer hidden. (#30635)
|
|
@ -89,20 +89,25 @@ public:
|
|||
|
||||
/**
|
||||
* 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
|
||||
* for the chain tip to differ from this.
|
||||
* @param[in] timeout how long to wait for a new tip
|
||||
* @returns Hash and height of the current chain tip after this call.
|
||||
* @param[in] timeout how long to wait for a new tip (default is forever)
|
||||
*
|
||||
* @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
|
||||
* @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;
|
||||
|
||||
|
|
|
@ -1070,24 +1070,41 @@ public:
|
|||
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
|
||||
auto deadline{std::chrono::steady_clock::now() + timeout};
|
||||
{
|
||||
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) {
|
||||
// We need to wait for m_tip_block to be set AND for the value
|
||||
// to differ from the current_tip value.
|
||||
return (notifications().TipBlock() && notifications().TipBlock() != current_tip) || chainman().m_interrupt;
|
||||
// For callers convenience, wait longer than the provided timeout
|
||||
// during startup for the tip to be non-null. That way this function
|
||||
// always returns valid tip information when possible and only
|
||||
// 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);
|
||||
return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};
|
||||
|
||||
if (chainman().m_interrupt) return {};
|
||||
|
||||
// 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
|
||||
{
|
||||
// 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};
|
||||
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);
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
using kernel::CCoinsStats;
|
||||
using kernel::CoinStatsHashType;
|
||||
|
||||
using interfaces::BlockRef;
|
||||
using interfaces::Mining;
|
||||
using node::BlockManager;
|
||||
using node::NodeContext;
|
||||
|
@ -265,6 +266,7 @@ static RPCHelpMan waitfornewblock()
|
|||
"\nMake sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",
|
||||
{
|
||||
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."},
|
||||
{"current_tip", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "Method waits for the chain tip to differ from this."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
@ -286,14 +288,33 @@ static RPCHelpMan waitfornewblock()
|
|||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
Mining& miner = EnsureMining(node);
|
||||
|
||||
auto block{CHECK_NONFATAL(miner.getTip()).value()};
|
||||
if (IsRPCRunning()) {
|
||||
block = timeout ? miner.waitTipChanged(block.hash, std::chrono::milliseconds(timeout)) : miner.waitTipChanged(block.hash);
|
||||
}
|
||||
/**
|
||||
* If the caller provided a current_tip value, pass it to waitTipChanged().
|
||||
*
|
||||
* If the caller did not provide a current tip hash, call getTip() to get
|
||||
* one and wait for the tip to be different from this value. This mode is
|
||||
* less reliable because if the tip changed between waitfornewblock calls,
|
||||
* it will need to change a second time before this call returns.
|
||||
*
|
||||
* Abort if RPC came out of warmup too early.
|
||||
*/
|
||||
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
|
||||
|
||||
uint256 tip_hash{request.params[1].isNull()
|
||||
? current_block.hash
|
||||
: ParseHashV(request.params[1], "current_tip")};
|
||||
|
||||
// If the user provided an invalid current_tip then this call immediately
|
||||
// returns the current tip.
|
||||
std::optional<BlockRef> block = timeout ? miner.waitTipChanged(tip_hash, std::chrono::milliseconds(timeout)) :
|
||||
miner.waitTipChanged(tip_hash);
|
||||
|
||||
// Return current block upon shutdown
|
||||
if (block) current_block = *block;
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("hash", block.hash.GetHex());
|
||||
ret.pushKV("height", block.height);
|
||||
ret.pushKV("hash", current_block.hash.GetHex());
|
||||
ret.pushKV("height", current_block.height);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
|
@ -332,22 +353,28 @@ static RPCHelpMan waitforblock()
|
|||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
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};
|
||||
while (IsRPCRunning() && block.hash != hash) {
|
||||
while (current_block.hash != hash) {
|
||||
std::optional<BlockRef> block;
|
||||
if (timeout) {
|
||||
auto now{std::chrono::steady_clock::now()};
|
||||
if (now >= deadline) break;
|
||||
const MillisecondsDouble remaining{deadline - now};
|
||||
block = miner.waitTipChanged(block.hash, remaining);
|
||||
block = miner.waitTipChanged(current_block.hash, remaining);
|
||||
} 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);
|
||||
ret.pushKV("hash", block.hash.GetHex());
|
||||
ret.pushKV("height", block.height);
|
||||
ret.pushKV("hash", current_block.hash.GetHex());
|
||||
ret.pushKV("height", current_block.height);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
|
@ -387,23 +414,29 @@ static RPCHelpMan waitforblockheight()
|
|||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
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};
|
||||
|
||||
while (IsRPCRunning() && block.height < height) {
|
||||
while (current_block.height < height) {
|
||||
std::optional<BlockRef> block;
|
||||
if (timeout) {
|
||||
auto now{std::chrono::steady_clock::now()};
|
||||
if (now >= deadline) break;
|
||||
const MillisecondsDouble remaining{deadline - now};
|
||||
block = miner.waitTipChanged(block.hash, remaining);
|
||||
block = miner.waitTipChanged(current_block.hash, remaining);
|
||||
} 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);
|
||||
ret.pushKV("hash", block.hash.GetHex());
|
||||
ret.pushKV("height", block.height);
|
||||
ret.pushKV("hash", current_block.hash.GetHex());
|
||||
ret.pushKV("height", current_block.height);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
|
@ -3408,9 +3441,9 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
|
|||
{"blockchain", &getchainstates},
|
||||
{"hidden", &invalidateblock},
|
||||
{"hidden", &reconsiderblock},
|
||||
{"hidden", &waitfornewblock},
|
||||
{"hidden", &waitforblock},
|
||||
{"hidden", &waitforblockheight},
|
||||
{"blockchain", &waitfornewblock},
|
||||
{"blockchain", &waitforblock},
|
||||
{"blockchain", &waitforblockheight},
|
||||
{"hidden", &syncwithvalidationinterfacequeue},
|
||||
};
|
||||
for (const auto& c : commands) {
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include <memory>
|
||||
#include <stdint.h>
|
||||
|
||||
using interfaces::BlockRef;
|
||||
using interfaces::BlockTemplate;
|
||||
using interfaces::Mining;
|
||||
using node::BlockAssembler;
|
||||
|
@ -775,9 +776,22 @@ static RPCHelpMan getblocktemplate()
|
|||
static unsigned int nTransactionsUpdatedLast;
|
||||
const CTxMemPool& mempool = EnsureMemPool(node);
|
||||
|
||||
if (!lpval.isNull())
|
||||
{
|
||||
// Wait to respond until either the best block changes, OR a minute has passed and there are more transactions
|
||||
// Long Polling (BIP22)
|
||||
if (!lpval.isNull()) {
|
||||
/**
|
||||
* 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;
|
||||
unsigned int nTransactionsUpdatedLastLP;
|
||||
|
||||
|
@ -786,6 +800,8 @@ static RPCHelpMan getblocktemplate()
|
|||
// Format: <hashBestChain><nTransactionsUpdatedLast>
|
||||
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");
|
||||
nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64));
|
||||
}
|
||||
|
@ -800,12 +816,20 @@ static RPCHelpMan getblocktemplate()
|
|||
LEAVE_CRITICAL_SECTION(cs_main);
|
||||
{
|
||||
MillisecondsDouble checktxtime{std::chrono::minutes(1)};
|
||||
while (tip == hashWatchedChain && IsRPCRunning()) {
|
||||
tip = miner.waitTipChanged(hashWatchedChain, checktxtime).hash;
|
||||
// Timeout: Check transactions for update
|
||||
// without holding the mempool lock to avoid deadlocks
|
||||
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP)
|
||||
while (IsRPCRunning()) {
|
||||
// If hashWatchedChain is not a real block hash, this will
|
||||
// return immediately.
|
||||
std::optional<BlockRef> maybe_tip{miner.waitTipChanged(hashWatchedChain, checktxtime)};
|
||||
// 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;
|
||||
}
|
||||
checktxtime = std::chrono::seconds(10);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -578,7 +578,8 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
node.reconsiderblock(rollback_hash)
|
||||
# The chain has probably already been restored by the time reconsiderblock returns,
|
||||
# but poll anyway.
|
||||
self.wait_until(lambda: node.waitfornewblock(timeout=100)['hash'] == current_hash)
|
||||
self.wait_until(lambda: node.waitfornewblock(current_tip=rollback_header['previousblockhash'])['hash'] == current_hash)
|
||||
|
||||
assert_raises_rpc_error(-1, "Negative timeout", node.waitfornewblock, -1)
|
||||
|
||||
def _test_waitforblockheight(self):
|
||||
|
|
Loading…
Add table
Reference in a new issue