Skip to content

Commit 99a4ddf

Browse files
committed
Merge bitcoin/bitcoin#31785: Have createNewBlock() wait for tip, make rpc handle shutdown during long poll and wait methods
05117e6 rpc: clarify longpoll behavior (Sjors Provoost) 5315278 Have createNewBlock() wait for a tip (Sjors Provoost) 64a2795 rpc: handle shutdown during long poll and wait methods (Sjors Provoost) a3bf433 rpc: drop unneeded IsRPCRunning() guards (Sjors Provoost) f9cf8bd Handle negative timeout for waitTipChanged() (Sjors Provoost) Pull request description: This PR prevents Mining interface methods from sometimes crashing when called during startup before a tip is connected. It also makes other improvements like making more RPC methods usable from the GUI. Specifically this PR: - Adds an `Assume` check to disallow passing negative timeout values to `Mining::waitTipChanged` - Makes `waitfornewblock`, `waitforblock` and `waitforblockheight` RPC methods usable from the GUI when `-server=1` is not set. - Changes `Mining::waitTipChanged` to return `optional<BlockRef>` instead of `BlockRef` and return `nullopt` instead of crashing if there is a timeout or if the node is shut down before a tip is connected. - Changes `Mining::waitTipChanged` to not time out before a tip is connected, so it is convenient and safe to call during startup, and only returns `nullopt` on early shutdowns. - Changes `Mining::createNewBlock` to block and wait for a tip to be connected if it is called on startup instead of crashing. Also documents that it will return null on early shutdowns. This allows `waitNext()` (added in bitcoin/bitcoin#31283) to safely assume `TipBlock()` isn't `null`, not even during a scenario of early shutdown. Finally this PR clarifies long poll behaviour, mostly by adding code comments, but also through an early `break`. ACKs for top commit: achow101: ACK 05117e6 ryanofsky: Code review ACK 05117e6, just updated a commit message since last review TheCharlatan: ACK 05117e6 vasild: ACK 05117e6 Tree-SHA512: 277c285a6e73dfff88fd379298190b264254996f98b93c91c062986ab35c2aa5e1fbfec4cd71d7b29dc2d68e33f252b5cfc501345f54939d6bd78599b71fec04
2 parents 22770ce + 05117e6 commit 99a4ddf

File tree

4 files changed

+102
-40
lines changed

4 files changed

+102
-40
lines changed

src/interfaces/mining.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,25 @@ class Mining
9292

9393
/**
9494
* Waits for the connected tip to change. During node initialization, this will
95-
* wait until the tip is connected.
95+
* wait until the tip is connected (regardless of `timeout`).
9696
*
9797
* @param[in] current_tip block hash of the current chain tip. Function waits
9898
* for the chain tip to differ from this.
99-
* @param[in] timeout how long to wait for a new tip
100-
* @returns Hash and height of the current chain tip after this call.
99+
* @param[in] timeout how long to wait for a new tip (default is forever)
100+
*
101+
* @retval BlockRef hash and height of the current chain tip after this call.
102+
* @retval std::nullopt if the node is shut down.
101103
*/
102-
virtual BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
104+
virtual std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
103105

104106
/**
105-
* Construct a new block template
107+
* Construct a new block template.
108+
*
109+
* During node initialization, this will wait until the tip is connected.
106110
*
107111
* @param[in] options options for creating the block
108-
* @returns a block template
112+
* @retval BlockTemplate a block template.
113+
* @retval std::nullptr if the node is shut down.
109114
*/
110115
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}) = 0;
111116

src/node/interfaces.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,24 +1070,41 @@ class MinerImpl : public Mining
10701070
return BlockRef{tip->GetBlockHash(), tip->nHeight};
10711071
}
10721072

1073-
BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
1073+
std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
10741074
{
1075+
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
1076+
if (timeout < 0ms) timeout = 0ms;
10751077
if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
1078+
auto deadline{std::chrono::steady_clock::now() + timeout};
10761079
{
10771080
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
1078-
notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
1079-
// We need to wait for m_tip_block to be set AND for the value
1080-
// to differ from the current_tip value.
1081-
return (notifications().TipBlock() && notifications().TipBlock() != current_tip) || chainman().m_interrupt;
1081+
// For callers convenience, wait longer than the provided timeout
1082+
// during startup for the tip to be non-null. That way this function
1083+
// always returns valid tip information when possible and only
1084+
// returns null when shutting down, not when timing out.
1085+
notifications().m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
1086+
return notifications().TipBlock() || chainman().m_interrupt;
1087+
});
1088+
if (chainman().m_interrupt) return {};
1089+
// At this point TipBlock is set, so continue to wait until it is
1090+
// different then `current_tip` provided by caller.
1091+
notifications().m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
1092+
return Assume(notifications().TipBlock()) != current_tip || chainman().m_interrupt;
10821093
});
10831094
}
1084-
// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
1085-
LOCK(::cs_main);
1086-
return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};
1095+
1096+
if (chainman().m_interrupt) return {};
1097+
1098+
// Must release m_tip_block_mutex before getTip() locks cs_main, to
1099+
// avoid deadlocks.
1100+
return getTip();
10871101
}
10881102

10891103
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
10901104
{
1105+
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
1106+
if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};
1107+
10911108
BlockAssembler::Options assemble_options{options};
10921109
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
10931110
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);

src/rpc/blockchain.cpp

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
using kernel::CCoinsStats;
6565
using kernel::CoinStatsHashType;
6666

67+
using interfaces::BlockRef;
6768
using interfaces::Mining;
6869
using node::BlockManager;
6970
using node::NodeContext;
@@ -286,14 +287,17 @@ static RPCHelpMan waitfornewblock()
286287
NodeContext& node = EnsureAnyNodeContext(request.context);
287288
Mining& miner = EnsureMining(node);
288289

289-
auto block{CHECK_NONFATAL(miner.getTip()).value()};
290-
if (IsRPCRunning()) {
291-
block = timeout ? miner.waitTipChanged(block.hash, std::chrono::milliseconds(timeout)) : miner.waitTipChanged(block.hash);
292-
}
290+
// Abort if RPC came out of warmup too early
291+
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
292+
std::optional<BlockRef> block = timeout ? miner.waitTipChanged(current_block.hash, std::chrono::milliseconds(timeout)) :
293+
miner.waitTipChanged(current_block.hash);
294+
295+
// Return current block upon shutdown
296+
if (block) current_block = *block;
293297

294298
UniValue ret(UniValue::VOBJ);
295-
ret.pushKV("hash", block.hash.GetHex());
296-
ret.pushKV("height", block.height);
299+
ret.pushKV("hash", current_block.hash.GetHex());
300+
ret.pushKV("height", current_block.height);
297301
return ret;
298302
},
299303
};
@@ -332,22 +336,28 @@ static RPCHelpMan waitforblock()
332336
NodeContext& node = EnsureAnyNodeContext(request.context);
333337
Mining& miner = EnsureMining(node);
334338

335-
auto block{CHECK_NONFATAL(miner.getTip()).value()};
339+
// Abort if RPC came out of warmup too early
340+
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
341+
336342
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
337-
while (IsRPCRunning() && block.hash != hash) {
343+
while (current_block.hash != hash) {
344+
std::optional<BlockRef> block;
338345
if (timeout) {
339346
auto now{std::chrono::steady_clock::now()};
340347
if (now >= deadline) break;
341348
const MillisecondsDouble remaining{deadline - now};
342-
block = miner.waitTipChanged(block.hash, remaining);
349+
block = miner.waitTipChanged(current_block.hash, remaining);
343350
} else {
344-
block = miner.waitTipChanged(block.hash);
351+
block = miner.waitTipChanged(current_block.hash);
345352
}
353+
// Return current block upon shutdown
354+
if (!block) break;
355+
current_block = *block;
346356
}
347357

348358
UniValue ret(UniValue::VOBJ);
349-
ret.pushKV("hash", block.hash.GetHex());
350-
ret.pushKV("height", block.height);
359+
ret.pushKV("hash", current_block.hash.GetHex());
360+
ret.pushKV("height", current_block.height);
351361
return ret;
352362
},
353363
};
@@ -387,23 +397,29 @@ static RPCHelpMan waitforblockheight()
387397
NodeContext& node = EnsureAnyNodeContext(request.context);
388398
Mining& miner = EnsureMining(node);
389399

390-
auto block{CHECK_NONFATAL(miner.getTip()).value()};
400+
// Abort if RPC came out of warmup too early
401+
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
402+
391403
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
392404

393-
while (IsRPCRunning() && block.height < height) {
405+
while (current_block.height < height) {
406+
std::optional<BlockRef> block;
394407
if (timeout) {
395408
auto now{std::chrono::steady_clock::now()};
396409
if (now >= deadline) break;
397410
const MillisecondsDouble remaining{deadline - now};
398-
block = miner.waitTipChanged(block.hash, remaining);
411+
block = miner.waitTipChanged(current_block.hash, remaining);
399412
} else {
400-
block = miner.waitTipChanged(block.hash);
413+
block = miner.waitTipChanged(current_block.hash);
401414
}
415+
// Return current block on shutdown
416+
if (!block) break;
417+
current_block = *block;
402418
}
403419

404420
UniValue ret(UniValue::VOBJ);
405-
ret.pushKV("hash", block.hash.GetHex());
406-
ret.pushKV("height", block.height);
421+
ret.pushKV("hash", current_block.hash.GetHex());
422+
ret.pushKV("height", current_block.height);
407423
return ret;
408424
},
409425
};

src/rpc/mining.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <memory>
4747
#include <stdint.h>
4848

49+
using interfaces::BlockRef;
4950
using interfaces::BlockTemplate;
5051
using interfaces::Mining;
5152
using node::BlockAssembler;
@@ -775,9 +776,22 @@ static RPCHelpMan getblocktemplate()
775776
static unsigned int nTransactionsUpdatedLast;
776777
const CTxMemPool& mempool = EnsureMemPool(node);
777778

778-
if (!lpval.isNull())
779-
{
780-
// Wait to respond until either the best block changes, OR a minute has passed and there are more transactions
779+
// Long Polling (BIP22)
780+
if (!lpval.isNull()) {
781+
/**
782+
* Wait to respond until either the best block changes, OR there are more
783+
* transactions.
784+
*
785+
* The check for new transactions first happens after 1 minute and
786+
* subsequently every 10 seconds. BIP22 does not require this particular interval.
787+
* On mainnet the mempool changes frequently enough that in practice this RPC
788+
* returns after 60 seconds, or sooner if the best block changes.
789+
*
790+
* getblocktemplate is unlikely to be called by bitcoin-cli, so
791+
* -rpcclienttimeout is not a concern. BIP22 recommends a long request timeout.
792+
*
793+
* The longpollid is assumed to be a tip hash if it has the right format.
794+
*/
781795
uint256 hashWatchedChain;
782796
unsigned int nTransactionsUpdatedLastLP;
783797

@@ -786,6 +800,8 @@ static RPCHelpMan getblocktemplate()
786800
// Format: <hashBestChain><nTransactionsUpdatedLast>
787801
const std::string& lpstr = lpval.get_str();
788802

803+
// Assume the longpollid is a block hash. If it's not then we return
804+
// early below.
789805
hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid");
790806
nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64));
791807
}
@@ -800,12 +816,20 @@ static RPCHelpMan getblocktemplate()
800816
LEAVE_CRITICAL_SECTION(cs_main);
801817
{
802818
MillisecondsDouble checktxtime{std::chrono::minutes(1)};
803-
while (tip == hashWatchedChain && IsRPCRunning()) {
804-
tip = miner.waitTipChanged(hashWatchedChain, checktxtime).hash;
805-
// Timeout: Check transactions for update
806-
// without holding the mempool lock to avoid deadlocks
807-
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP)
819+
while (IsRPCRunning()) {
820+
// If hashWatchedChain is not a real block hash, this will
821+
// return immediately.
822+
std::optional<BlockRef> maybe_tip{miner.waitTipChanged(hashWatchedChain, checktxtime)};
823+
// Node is shutting down
824+
if (!maybe_tip) break;
825+
tip = maybe_tip->hash;
826+
if (tip != hashWatchedChain) break;
827+
828+
// Check transactions for update without holding the mempool
829+
// lock to avoid deadlocks.
830+
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) {
808831
break;
832+
}
809833
checktxtime = std::chrono::seconds(10);
810834
}
811835
}

0 commit comments

Comments
 (0)