Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test the IPC (multiprocess) Mining interface."""6import asyncio7import time8from contextlib import AsyncExitStack9from copy import deepcopy10from decimal import Decimal11from io import BytesIO12from test_framework.blocktools import (13NULL_OUTPOINT,14script_BIP34_coinbase_height,15WITNESS_COMMITMENT_HEADER,16)17from test_framework.messages import (18CBlockHeader,19COIN,20CTransaction,21CTxIn,22CTxInWitness,23CTxOut,24DEFAULT_BLOCK_RESERVED_WEIGHT,25MAX_BLOCK_SIGOPS_COST,26MAX_BLOCK_WEIGHT,27from_hex,28msg_headers,29ser_uint256,30)31from test_framework.test_framework import BitcoinTestFramework32from test_framework.util import (33assert_equal,34assert_greater_than_or_equal,35assert_not_equal,36)37from test_framework.wallet import MiniWallet38from test_framework.p2p import P2PInterface39from test_framework.ipc_util import (40assert_capnp_failed,41assert_create_new_block_fails,42destroying,43load_capnp_modules,44make_mining_ctx,45mining_create_block_template,46mining_get_block,47mining_get_coinbase_tx,48mining_wait_next_template,49wait_and_do,50)52# Test may be skipped and not have capnp installed53try:54import capnp # type: ignore[import] # noqa: F40155except ModuleNotFoundError:56pass59class IPCMiningTest(BitcoinTestFramework):61def skip_test_if_missing_module(self):62self.skip_if_no_ipc()63self.skip_if_no_py_capnp()65def set_test_params(self):66self.num_nodes = 368def setup_nodes(self):69self.extra_init = [{"ipcbind": True}, {}, {"ipcbind": True}]70super().setup_nodes()71# Use this function to also load the capnp modules (we cannot use set_test_params for this,72# as it is being called before knowing whether capnp is available).73self.capnp_modules = load_capnp_modules(self.config)75async def build_coinbase_test(self, template, ctx, miniwallet, extra_nonce=b""):76self.log.debug("Build coinbase transaction using getCoinbaseTx()")77assert template is not None78coinbase_res = await mining_get_coinbase_tx(template, ctx)79coinbase_tx = CTransaction()80coinbase_tx.version = coinbase_res.version81coinbase_tx.vin = [CTxIn()]82coinbase_tx.vin[0].prevout = NULL_OUTPOINT83coinbase_tx.vin[0].nSequence = coinbase_res.sequence85# Verify there's no dummy extraNonce in the coinbase scriptSig86current_block_height = self.nodes[0].getchaintips()[0]["height"]87bip34_prefix = script_BIP34_coinbase_height(current_block_height + 1, padding=False)88assert_equal(coinbase_res.scriptSigPrefix, bip34_prefix)90# Typically a mining pool appends its name and an extraNonce91coinbase_tx.vin[0].scriptSig = coinbase_res.scriptSigPrefix + extra_nonce93# We currently always provide a coinbase witness, even for empty94# blocks, but this may change, so always check:95has_witness = coinbase_res.witness is not None96if has_witness:97coinbase_tx.wit.vtxinwit = [CTxInWitness()]98coinbase_tx.wit.vtxinwit[0].scriptWitness.stack = [coinbase_res.witness]100# First output is our payout101coinbase_tx.vout = [CTxOut()]102coinbase_tx.vout[0].scriptPubKey = miniwallet.get_output_script()103coinbase_tx.vout[0].nValue = coinbase_res.blockRewardRemaining104# Add SegWit OP_RETURN. This is currently always present even for105# empty blocks, but this may change.106for output_data in coinbase_res.requiredOutputs:107output = CTxOut()108output.deserialize(BytesIO(output_data))109coinbase_tx.vout.append(output)111coinbase_tx.nLockTime = coinbase_res.lockTime112return coinbase_tx114async def build_candidate_block(self, template, ctx, extra_nonce=b""):115"""Build a complete block from a remote BlockTemplate.117The returned block replaces the dummy coinbase from CreateNewBlock()118with one constructed from getCoinbaseTx().119"""120block = await mining_get_block(template, ctx)121coinbase = await self.build_coinbase_test(122template, ctx, self.miniwallet, extra_nonce=extra_nonce)123# Reduce payout for balance comparison simplicity.124coinbase.vout[0].nValue = COIN125block.vtx[0] = coinbase126block.hashMerkleRoot = block.calc_merkle_root()127return block129async def assert_submit_block(self, mining, ctx, block, *, result, reason="", debug=""):130submit = await mining.submitBlock(ctx, block.serialize())131assert_equal(submit.result, result)132assert_equal(submit.reason, reason)133assert_equal(submit.debug, debug)135def run_mining_interface_test(self):136"""Test Mining interface methods."""137self.log.info("Running Mining interface test")138block_hash_size = 32140async def async_routine():141ctx, mining = await make_mining_ctx(self)142blockref = await mining.getTip(ctx)143current_block_height = self.nodes[0].getchaintips()[0]["height"]144assert_equal(blockref.result.height, current_block_height)146self.log.debug("Mine a block")147newblockref = (await wait_and_do(148mining.waitTipChanged(ctx, blockref.result.hash, self.default_ipc_timeout),149lambda: self.generate(self.nodes[0], 1))).result150assert_equal(len(newblockref.hash), block_hash_size)151assert_equal(newblockref.height, current_block_height + 1)152self.log.debug("Wait for timeout")153oldblockref = (await mining.waitTipChanged(ctx, newblockref.hash, self.default_ipc_timeout)).result154assert_equal(len(newblockref.hash), block_hash_size)155assert_equal(oldblockref.hash, newblockref.hash)156assert_equal(oldblockref.height, newblockref.height)158self.log.debug("interrupt() should abort waitTipChanged()")159async def wait_for_tip():160long_timeout = max(self.default_ipc_timeout, 60000.0) # at least 1 minute161result = (await mining.waitTipChanged(ctx, newblockref.hash, long_timeout)).result162# Unlike a timeout, interrupt() returns an empty BlockRef.163assert_equal(len(result.hash), 0)164await wait_and_do(wait_for_tip(), mining.interrupt())166asyncio.run(capnp.run(async_routine()))168def run_early_startup_test(self):169"""Make sure mining.createNewBlock safely returns on early startup as170soon as mining interface is available """171self.log.info("Running Mining interface early startup test")173node = self.nodes[0]174self.stop_node(node.index)175node.start()177async def async_routine():178while True:179try:180ctx, mining = await make_mining_ctx(self)181break182except (ConnectionRefusedError, FileNotFoundError):183# Poll quickly to connect as soon as socket becomes184# available but without using a lot of CPU185await asyncio.sleep(0.005)187opts = self.capnp_modules['mining'].BlockCreateOptions()188await mining.createNewBlock(ctx, opts)190asyncio.run(capnp.run(async_routine()))192# Reconnect nodes so next tests are happy193node.wait_for_rpc_connection()194self.connect_nodes(1, 0)195# Restarting node 0 drops its P2P connection to the rest of the test196# chain. Restore the synced 0-1-2 topology before later tests split197# node 2 off for submitBlock checks.198self.sync_all()200def run_block_template_test(self):201"""Test BlockTemplate interface methods."""202self.log.info("Running BlockTemplate interface test")203block_header_size = 80205async def async_routine():206ctx, mining = await make_mining_ctx(self)208async with AsyncExitStack() as stack:209self.log.debug("createNewBlock() should wait if tip is still updating")210self.disconnect_nodes(0, 1)211node1_block_hash = self.generate(self.nodes[1], 1, sync_fun=self.no_op)[0]212header = from_hex(CBlockHeader(), self.nodes[1].getblockheader(node1_block_hash, False))213header_only_peer = self.nodes[0].add_p2p_connection(P2PInterface())214header_only_peer.send_and_ping(msg_headers([header]))215start = time.time()216async with destroying((await mining.createNewBlock(ctx, self.default_block_create_options)).result, ctx):217pass218# Lower-bound only: a heavily loaded CI host might still exceed 0.9s219# even without the cooldown, so this can miss regressions but avoids220# spurious failures.221assert_greater_than_or_equal(time.time() - start, 0.9)223self.log.debug("createNewBlock() should wake up promptly after tip advances")224success = False225duration = 0.0226async def wait_fn():227nonlocal success, duration228start = time.time()229res = await mining.createNewBlock(ctx, self.default_block_create_options)230duration = time.time() - start231success = res._has("result")232def do_fn():233block_hex = self.nodes[1].getblock(node1_block_hash, False)234self.nodes[0].submitblock(block_hex)235await wait_and_do(wait_fn(), do_fn)236assert_equal(success, True)237if self.options.timeout_factor <= 1:238assert duration < 3.0, f"createNewBlock took {duration:.2f}s, did not wake up promptly after tip advances"239else:240self.log.debug("Skipping strict wake-up duration check because timeout_factor > 1")242self.log.debug("interrupt() should abort createNewBlock() during cooldown")243async def create_block():244result = await mining.createNewBlock(ctx, self.default_block_create_options)245# interrupt() causes createNewBlock to return nullptr246assert_equal(result._has("result"), False)248await wait_and_do(create_block(), mining.interrupt())250header_only_peer.peer_disconnect()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.