Skip to content

Commit f24385e

Browse files
committed
mining: add interrupt()
Both waitTipChanged() and createNewBlock() can take a long time to return. Add a way for clients to interrupt them. The new m_interrupt_mining is safely accessed with a lock on m_tip_block_mutex, but it has no guard annotation. A more thorough solution is discussed here: bitcoin#34184 (comment)
1 parent 6eac1d5 commit f24385e

File tree

6 files changed

+58
-17
lines changed

6 files changed

+58
-17
lines changed

src/interfaces/mining.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class Mining
138138
* @param[in] timeout how long to wait for a new tip (default is forever)
139139
*
140140
* @retval BlockRef hash and height of the current chain tip after this call.
141-
* @retval std::nullopt if the node is shut down.
141+
* @retval std::nullopt if the node is shut down or interrupt() is called.
142142
*/
143143
virtual std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
144144

@@ -152,10 +152,15 @@ class Mining
152152
* regtest and signets with only one miner, as these
153153
* could stall.
154154
* @retval BlockTemplate a block template.
155-
* @retval std::nullptr if the node is shut down.
155+
* @retval std::nullptr if the node is shut down or interrupt() is called.
156156
*/
157157
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}, bool cooldown = true) = 0;
158158

159+
/**
160+
* Interrupts createNewBlock and waitTipChanged.
161+
*/
162+
virtual void interrupt() = 0;
163+
159164
/**
160165
* Checks if a given block is valid.
161166
*

src/ipc/capnp/mining.capnp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") {
1919
waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef);
2020
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);
22+
interrupt @6 () -> ();
2223
}
2324

2425
interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {

src/node/interfaces.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ class MinerImpl : public Mining
965965

966966
std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
967967
{
968-
return WaitTipChanged(chainman(), notifications(), current_tip, timeout);
968+
return WaitTipChanged(chainman(), notifications(), current_tip, timeout, m_interrupt_mining);
969969
}
970970

971971
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options, bool cooldown) override
@@ -993,18 +993,23 @@ class MinerImpl : public Mining
993993
// forever if no block was mined in the past day.
994994
while (chainman().IsInitialBlockDownload()) {
995995
maybe_tip = waitTipChanged(maybe_tip->hash, MillisecondsDouble{1000});
996-
if (!maybe_tip) return {};
996+
if (!maybe_tip || chainman().m_interrupt || WITH_LOCK(notifications().m_tip_block_mutex, return m_interrupt_mining)) return {};
997997
}
998998

999999
// Also wait during the final catch-up moments after IBD.
1000-
if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip)) return {};
1000+
if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip, m_interrupt_mining)) return {};
10011001
}
10021002

10031003
BlockAssembler::Options assemble_options{options};
10041004
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
10051005
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
10061006
}
10071007

1008+
void interrupt() override
1009+
{
1010+
InterruptWait(notifications(), m_interrupt_mining);
1011+
}
1012+
10081013
bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
10091014
{
10101015
LOCK(chainman().GetMutex());
@@ -1017,6 +1022,8 @@ class MinerImpl : public Mining
10171022
NodeContext* context() override { return &m_node; }
10181023
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
10191024
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
1025+
// Treat as if guarded by notifications().m_tip_block_mutex
1026+
bool m_interrupt_mining{false};
10201027
NodeContext& m_node;
10211028
};
10221029
} // namespace

src/node/miner.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ 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)
458+
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip, bool& interrupt_mining)
459459
{
460460
uint256 last_tip_hash{last_tip.hash};
461461

@@ -467,9 +467,12 @@ bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& ke
467467
WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
468468
kernel_notifications.m_tip_block_cv.wait_until(lock, cooldown_deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
469469
const auto tip_block = kernel_notifications.TipBlock();
470-
return chainman.m_interrupt || (tip_block && *tip_block != last_tip_hash);
470+
return chainman.m_interrupt || interrupt_mining || (tip_block && *tip_block != last_tip_hash);
471471
});
472-
if (chainman.m_interrupt) return false;
472+
if (chainman.m_interrupt || interrupt_mining) {
473+
interrupt_mining = false;
474+
return false;
475+
}
473476

474477
// If the tip changed during the wait, extend the deadline
475478
const auto tip_block = kernel_notifications.TipBlock();
@@ -486,7 +489,7 @@ bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& ke
486489
return true;
487490
}
488491

489-
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout)
492+
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
490493
{
491494
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
492495
if (timeout < 0ms) timeout = 0ms;
@@ -499,16 +502,22 @@ std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifi
499502
// always returns valid tip information when possible and only
500503
// returns null when shutting down, not when timing out.
501504
kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
502-
return kernel_notifications.TipBlock() || chainman.m_interrupt;
505+
return kernel_notifications.TipBlock() || chainman.m_interrupt || interrupt;
503506
});
504-
if (chainman.m_interrupt) return {};
507+
if (chainman.m_interrupt || interrupt) {
508+
interrupt = false;
509+
return {};
510+
}
505511
// At this point TipBlock is set, so continue to wait until it is
506512
// different then `current_tip` provided by caller.
507513
kernel_notifications.m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
508-
return Assume(kernel_notifications.TipBlock()) != current_tip || chainman.m_interrupt;
514+
return Assume(kernel_notifications.TipBlock()) != current_tip || chainman.m_interrupt || interrupt;
509515
});
516+
if (chainman.m_interrupt || interrupt) {
517+
interrupt = false;
518+
return {};
519+
}
510520
}
511-
if (chainman.m_interrupt) return {};
512521

513522
// Must release m_tip_block_mutex before getTip() locks cs_main, to
514523
// avoid deadlocks.

src/node/miner.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ void ApplyArgsManOptions(const ArgsManager& gArgs, BlockAssembler::Options& opti
142142
void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t version, uint32_t timestamp, uint32_t nonce);
143143

144144

145-
/* Interrupt the current wait for the next block template. */
145+
/* Interrupt a blocking call. */
146146
void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait);
147147
/**
148148
* Return a new block template when fees rise to a certain threshold or after a
@@ -160,8 +160,10 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
160160
std::optional<BlockRef> GetTip(ChainstateManager& chainman);
161161

162162
/* Waits for the connected tip to change until timeout has elapsed. During node initialization, this will wait until the tip is connected (regardless of `timeout`).
163-
* Returns the current tip, or nullopt if the node is shutting down. */
164-
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout);
163+
* Returns the current tip, or nullopt if the node is shutting down or interrupt()
164+
* is called.
165+
*/
166+
std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt);
165167

166168
/**
167169
* Wait while the best known header extends the current chain tip AND at least
@@ -178,10 +180,11 @@ std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifi
178180
* once per connected client. Subsequent templates are provided by waitNext().
179181
*
180182
* @param last_tip tip at the start of the cooldown window.
183+
* @param interrupt_mining set to true to interrupt the cooldown.
181184
*
182185
* @returns false if interrupted.
183186
*/
184-
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip);
187+
bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip, bool& interrupt_mining);
185188
} // namespace node
186189

187190
#endif // BITCOIN_NODE_MINER_H

test/functional/interface_ipc_mining.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ async def async_routine():
153153
assert_equal(oldblockref.hash, newblockref.hash)
154154
assert_equal(oldblockref.height, newblockref.height)
155155

156+
self.log.debug("interrupt() should abort waitTipChanged()")
157+
async def wait_for_tip():
158+
long_timeout = 60000.0 # 1 minute
159+
result = (await mining.waitTipChanged(ctx, newblockref.hash, long_timeout)).result
160+
# Unlike a timeout, interrupt() returns an empty BlockRef.
161+
assert_equal(len(result.hash), 0)
162+
await wait_and_do(wait_for_tip(), mining.interrupt())
163+
156164
asyncio.run(capnp.run(async_routine()))
157165

158166
def run_block_template_test(self):
@@ -179,6 +187,14 @@ async def async_routine():
179187
# spurious failures.
180188
assert_greater_than_or_equal(time.time() - start, 0.9)
181189

190+
self.log.debug("interrupt() should abort createNewBlock() during cooldown")
191+
async def create_block():
192+
result = await mining.createNewBlock(ctx, self.default_block_create_options, cooldown=True)
193+
# interrupt() causes createNewBlock to return nullptr
194+
assert_equal(result._has("result"), False)
195+
196+
await wait_and_do(create_block(), mining.interrupt())
197+
182198
header_only_peer.peer_disconnect()
183199
self.connect_nodes(0, 1)
184200
self.sync_all()

0 commit comments

Comments
 (0)