Skip to content

Commit 5a2f2e6

Browse files
committed
mining: pass missing context to BlockTemplate methods
This is breaking IPC interface change. Adding a context parameter ensures that these methods are run in their own thread and don't block other calls. They were missing for: - createNewBlock() - checkBlock() A later commit makes createNewBlock() potentially long running. This makes the lack of context parameter a problem, justifying a breaking change. Since this is a breaking API change, we also fix the same issue for checkBlock. This ensures that when checkBlock() receives a slow to validate block from an untrusted origin (the IPC connection itself is assumed to trusted), it will not block the IPC server (though validation itself still blocks cs_main). To make this change explicit, bump the version of makeMining from 2 to 3. Old clients calling the original ordinal will receive an error message suggesting they update. A test covers this behavior.
1 parent 3aa343c commit 5a2f2e6

File tree

6 files changed

+33
-9
lines changed

6 files changed

+33
-9
lines changed

src/interfaces/init.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class Init
3939
virtual Ipc* ipc() { return nullptr; }
4040
virtual bool canListenIpc() { return false; }
4141
virtual const char* exeName() { return nullptr; }
42+
virtual void makeMiningDeprecatedV2() {
43+
throw std::runtime_error("Old mining interface (@2) not supported. Please update your client!");
44+
}
4245
};
4346

4447
//! Return implementation of Init interface for the node process. If the argv

src/ipc/capnp/init.capnp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@ using Mining = import "mining.capnp";
1919
interface Init $Proxy.wrap("interfaces::Init") {
2020
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
2121
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
22-
makeMining @2 (context :Proxy.Context) -> (result :Mining.Mining);
22+
makeMining @3 (context :Proxy.Context) -> (result :Mining.Mining);
23+
24+
# DEPRECATED: no longer supported; server returns an error.
25+
makeMiningDeprecatedV2 @2 () -> ();
2326
}

src/ipc/capnp/mining.capnp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ interface Mining $Proxy.wrap("interfaces::Mining") {
1717
isInitialBlockDownload @1 (context :Proxy.Context) -> (result: Bool);
1818
getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool);
1919
waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef);
20-
createNewBlock @4 (options: BlockCreateOptions) -> (result: BlockTemplate);
21-
checkBlock @5 (block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool);
20+
createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions) -> (result: BlockTemplate);
21+
checkBlock @5 (context :Proxy.Context, block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool);
2222
}
2323

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

test/functional/interface_ipc.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,27 @@ async def async_routine():
6868

6969
asyncio.run(capnp.run(async_routine()))
7070

71+
def run_deprecated_mining_test(self):
72+
self.log.info("Running deprecated mining interface test")
73+
async def async_routine():
74+
ctx, init = await make_capnp_init_ctx(self)
75+
self.log.debug("Calling deprecated makeMiningDeprecatedV2 should raise an error")
76+
try:
77+
await init.makeMiningDeprecatedV2()
78+
assert False, "Expected an exception"
79+
except capnp.KjException as e:
80+
assert "Old mining interface" in str(e)
81+
asyncio.run(capnp.run(async_routine()))
82+
7183
def run_test(self):
7284
self.run_echo_test()
7385
self.run_mining_test()
7486

87+
# Run at the end: the exception triggers a disconnect which causes a race
88+
# in libmultiprocess thread cleanup. See:
89+
# https://github.com/bitcoin-core/libmultiprocess/issues/189
90+
# https://github.com/bitcoin/bitcoin/pull/34284
91+
self.run_deprecated_mining_test()
92+
7593
if __name__ == '__main__':
7694
IPCInterfaceTest(__file__).main()

test/functional/interface_ipc_mining.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ async def async_routine():
261261
self.log.debug("Enforce minimum reserved weight for IPC clients too")
262262
opts.blockReservedWeight = 0
263263
try:
264-
await mining.createNewBlock(opts)
264+
await mining.createNewBlock(ctx, opts)
265265
raise AssertionError("createNewBlock unexpectedly succeeded")
266266
except capnp.lib.capnp.KjException as e:
267267
if e.type == "DISCONNECTED":
@@ -288,7 +288,7 @@ async def async_routine():
288288
current_block_height = self.nodes[0].getchaintips()[0]["height"]
289289
check_opts = self.capnp_modules['mining'].BlockCheckOptions()
290290

291-
async with destroying((await mining.createNewBlock(self.default_block_create_options)).result, ctx) as template:
291+
async with destroying((await mining.createNewBlock(ctx, self.default_block_create_options)).result, ctx) as template:
292292
block = await mining_get_block(template, ctx)
293293
balance = self.miniwallet.get_balance()
294294
coinbase = await self.build_coinbase_test(template, ctx, self.miniwallet)
@@ -301,7 +301,7 @@ async def async_routine():
301301
self.log.debug("Submit a block with a bad version")
302302
block.nVersion = 0
303303
block.solve()
304-
check = await mining.checkBlock(block.serialize(), check_opts)
304+
check = await mining.checkBlock(ctx, block.serialize(), check_opts)
305305
assert_equal(check.result, False)
306306
assert_equal(check.reason, "bad-version(0x00000000)")
307307
submitted = (await template.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())).result
@@ -311,7 +311,7 @@ async def async_routine():
311311
block.solve()
312312

313313
self.log.debug("First call checkBlock()")
314-
block_valid = (await mining.checkBlock(block.serialize(), check_opts)).result
314+
block_valid = (await mining.checkBlock(ctx, block.serialize(), check_opts)).result
315315
assert_equal(block_valid, True)
316316

317317
# The remote template block will be mutated, capture the original:
@@ -343,7 +343,7 @@ async def async_routine():
343343
self.miniwallet.rescan_utxos()
344344
assert_equal(self.miniwallet.get_balance(), balance + 1)
345345
self.log.debug("Check block should fail now, since it is a duplicate")
346-
check = await mining.checkBlock(block.serialize(), check_opts)
346+
check = await mining.checkBlock(ctx, block.serialize(), check_opts)
347347
assert_equal(check.result, False)
348348
assert_equal(check.reason, "inconclusive-not-best-prevblk")
349349

test/functional/test_framework/ipc_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ async def make_capnp_init_ctx(self):
108108

109109
async def mining_create_block_template(mining, stack, ctx, opts):
110110
"""Call mining.createNewBlock() and return template, then call template.destroy() when stack exits."""
111-
response = await mining.createNewBlock(opts)
111+
response = await mining.createNewBlock(ctx, opts)
112112
if not response._has("result"):
113113
return None
114114
return await stack.enter_async_context(destroying(response.result, ctx))

0 commit comments

Comments
 (0)