diff --git a/doc/release-notes-33819.md b/doc/release-notes-33819.md deleted file mode 100644 index 79ed1f70749..00000000000 --- a/doc/release-notes-33819.md +++ /dev/null @@ -1,8 +0,0 @@ -Mining IPC ----------- - -- The `getCoinbaseTx()` method is renamed to `getCoinbaseRawTx()` and deprecated. - IPC clients do not use the function name, so they're not affected. (#33819) -- Adds `getCoinbaseTx()` which clients should use instead of `getCoinbaseRawTx()`. It - contains all fields required to construct a coinbase transaction, and omits the - dummy output which Bitcoin Core uses internally. (#33819) diff --git a/doc/release-notes-34568.md b/doc/release-notes-34568.md new file mode 100644 index 00000000000..e48772330c1 --- /dev/null +++ b/doc/release-notes-34568.md @@ -0,0 +1,11 @@ +Mining IPC +---------- + +The IPC mining interface now requires mining clients to use the latest `mining.capnp` schema. Clients built against older schemas will fail when calling `Init.makeMining` and receive an RPC error indicating the old mining interface is no longer supported. Mining clients must update to the latest schema and regenerate bindings to continue working. (#34568) + +Notable IPC mining interface changes since the last release: +- `Mining.createNewBlock` and `Mining.checkBlock` now require a `context` parameter. +- `Mining.waitTipChanged` now has a default `timeout` (effectively infinite / `maxDouble`) if the client omits it. +- `BlockTemplate.getCoinbaseTx()` now returns a structured `CoinbaseTx` instead of raw bytes. +- Removed `BlockTemplate.getCoinbaseCommitment()` and `BlockTemplate.getWitnessCommitmentIndex()`. +- Cap’n Proto default values were updated to match the corresponding C++ defaults for mining-related option structs (e.g. `BlockCreateOptions`, `BlockWaitOptions`, `BlockCheckOptions`). diff --git a/src/interfaces/init.h b/src/interfaces/init.h index b909c9e6f6b..463d43e7c2e 100644 --- a/src/interfaces/init.h +++ b/src/interfaces/init.h @@ -39,6 +39,7 @@ public: virtual Ipc* ipc() { return nullptr; } virtual bool canListenIpc() { return false; } virtual const char* exeName() { return nullptr; } + virtual void makeMiningOld2() { throw std::runtime_error("Old mining interface (@2) not supported. Please update your client!"); } }; //! Return implementation of Init interface for the node process. If the argv diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index 993f70bd7aa..42002284d11 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -42,31 +42,9 @@ public: // Sigop cost per transaction, not including coinbase transaction. virtual std::vector getTxSigops() = 0; - /** - * Return serialized dummy coinbase transaction. - * - * @note deprecated: use getCoinbaseTx() - */ - virtual CTransactionRef getCoinbaseRawTx() = 0; - /** Return fields needed to construct a coinbase transaction */ virtual node::CoinbaseTx getCoinbaseTx() = 0; - /** - * Return scriptPubKey with SegWit OP_RETURN. - * - * @note deprecated: use getCoinbaseTx() - */ - virtual std::vector getCoinbaseCommitment() = 0; - - /** - * Return which output in the dummy coinbase contains the SegWit OP_RETURN. - * - * @note deprecated. Scan outputs from getCoinbaseTx() outputs field for the - * SegWit marker. - */ - virtual int getWitnessCommitmentIndex() = 0; - /** * Compute merkle path to the coinbase transaction * diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp index 64a7bf9b2b4..a20ef2fcaf3 100644 --- a/src/ipc/capnp/init.capnp +++ b/src/ipc/capnp/init.capnp @@ -19,5 +19,8 @@ using Mining = import "mining.capnp"; interface Init $Proxy.wrap("interfaces::Init") { construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo); - makeMining @2 (context :Proxy.Context) -> (result :Mining.Mining); + makeMining @3 (context :Proxy.Context) -> (result :Mining.Mining); + + # DEPRECATED: no longer supported; server returns an error. + makeMiningOld2 @2 () -> (); } diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp index 25df1186331..47cd242b81d 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -12,13 +12,18 @@ using Proxy = import "/mp/proxy.capnp"; $Proxy.include("interfaces/mining.h"); $Proxy.includeTypes("ipc/capnp/mining-types.h"); +const maxMoney :Int64 = 2100000000000000; +const maxDouble :Float64 = 1.7976931348623157e308; +const defaultBlockReservedWeight :UInt32 = 8000; +const defaultCoinbaseOutputMaxAdditionalSigops :UInt32 = 400; + interface Mining $Proxy.wrap("interfaces::Mining") { isTestChain @0 (context :Proxy.Context) -> (result: Bool); isInitialBlockDownload @1 (context :Proxy.Context) -> (result: Bool); getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool); - waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef); - createNewBlock @4 (options: BlockCreateOptions) -> (result: BlockTemplate); - checkBlock @5 (block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool); + waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64 = .maxDouble) -> (result: Common.BlockRef); + createNewBlock @4 (context :Proxy.Context, options: BlockCreateOptions) -> (result: BlockTemplate); + checkBlock @5 (context :Proxy.Context, block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool); } interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { @@ -27,30 +32,27 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { getBlock @2 (context: Proxy.Context) -> (result: Data); getTxFees @3 (context: Proxy.Context) -> (result: List(Int64)); getTxSigops @4 (context: Proxy.Context) -> (result: List(Int64)); - getCoinbaseRawTx @5 (context: Proxy.Context) -> (result: Data); - getCoinbaseTx @12 (context: Proxy.Context) -> (result: CoinbaseTx); - getCoinbaseCommitment @6 (context: Proxy.Context) -> (result: Data); - getWitnessCommitmentIndex @7 (context: Proxy.Context) -> (result: Int32); - getCoinbaseMerklePath @8 (context: Proxy.Context) -> (result: List(Data)); - submitSolution @9 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool); - waitNext @10 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate); - interruptWait @11() -> (); + getCoinbaseTx @5 (context: Proxy.Context) -> (result: CoinbaseTx); + getCoinbaseMerklePath @6 (context: Proxy.Context) -> (result: List(Data)); + submitSolution @7 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool); + waitNext @8 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate); + interruptWait @9() -> (); } struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") { - useMempool @0 :Bool $Proxy.name("use_mempool"); - blockReservedWeight @1 :UInt64 $Proxy.name("block_reserved_weight"); - coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops"); + useMempool @0 :Bool = true $Proxy.name("use_mempool"); + blockReservedWeight @1 :UInt64 = .defaultBlockReservedWeight $Proxy.name("block_reserved_weight"); + coinbaseOutputMaxAdditionalSigops @2 :UInt64 = .defaultCoinbaseOutputMaxAdditionalSigops $Proxy.name("coinbase_output_max_additional_sigops"); } struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") { - timeout @0 : Float64 $Proxy.name("timeout"); - feeThreshold @1 : Int64 $Proxy.name("fee_threshold"); + timeout @0 : Float64 = .maxDouble $Proxy.name("timeout"); + feeThreshold @1 : Int64 = .maxMoney $Proxy.name("fee_threshold"); } struct BlockCheckOptions $Proxy.wrap("node::BlockCheckOptions") { - checkMerkleRoot @0 :Bool $Proxy.name("check_merkle_root"); - checkPow @1 :Bool $Proxy.name("check_pow"); + checkMerkleRoot @0 :Bool = true $Proxy.name("check_merkle_root"); + checkPow @1 :Bool = true $Proxy.name("check_pow"); } struct CoinbaseTx $Proxy.wrap("node::CoinbaseTx") { diff --git a/src/ipc/test/ipc_test.cpp b/src/ipc/test/ipc_test.cpp index bec288813ad..31607299ae7 100644 --- a/src/ipc/test/ipc_test.cpp +++ b/src/ipc/test/ipc_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,11 @@ #include +static_assert(ipc::capnp::messages::MAX_MONEY == MAX_MONEY); +static_assert(ipc::capnp::messages::MAX_DOUBLE == std::numeric_limits::max()); +static_assert(ipc::capnp::messages::DEFAULT_BLOCK_RESERVED_WEIGHT == DEFAULT_BLOCK_RESERVED_WEIGHT); +static_assert(ipc::capnp::messages::DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS == DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS); + //! Remote init class. class TestInit : public interfaces::Init { diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index af9388d4b9f..8f5406ab3c0 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -890,26 +890,11 @@ public: return m_block_template->vTxSigOpsCost; } - CTransactionRef getCoinbaseRawTx() override - { - return m_block_template->block.vtx[0]; - } - CoinbaseTx getCoinbaseTx() override { return m_block_template->m_coinbase_tx; } - std::vector getCoinbaseCommitment() override - { - return m_block_template->vchCoinbaseCommitment; - } - - int getWitnessCommitmentIndex() override - { - return GetWitnessCommitmentIndex(m_block_template->block); - } - std::vector getCoinbaseMerklePath() override { return TransactionMerklePath(m_block_template->block, 0); diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 7bdd8b690d7..fe26794528b 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -197,7 +197,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock() coinbase_tx.lock_time = coinbaseTx.nLockTime; pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); - pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev); + m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev); const CTransactionRef& final_coinbase{pblock->vtx[0]}; if (final_coinbase->HasWitness()) { diff --git a/src/node/miner.h b/src/node/miner.h index 0c268f1826d..5a338dfb917 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -46,7 +46,6 @@ struct CBlockTemplate std::vector vTxFees; // Sigops per transaction, not including coinbase transaction (unlike CBlock::vtx). std::vector vTxSigOpsCost; - std::vector vchCoinbaseCommitment; /* A vector of package fee rates, ordered by the sequence in which * packages are selected for inclusion in the block template.*/ std::vector m_package_feerates; diff --git a/src/node/types.h b/src/node/types.h index e3ee05dd0d9..01e74528e06 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -55,7 +55,7 @@ struct BlockCreateOptions { * The maximum additional sigops which the pool will add in coinbase * transaction outputs. */ - size_t coinbase_output_max_additional_sigops{400}; + size_t coinbase_output_max_additional_sigops{DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS}; /** * Script to put in the coinbase transaction. The default is an * anyone-can-spend dummy. diff --git a/src/policy/policy.h b/src/policy/policy.h index 35189d71335..0bc7b327708 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -24,6 +24,8 @@ class CScript; static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT}; /** Default for -blockreservedweight **/ static constexpr unsigned int DEFAULT_BLOCK_RESERVED_WEIGHT{8000}; +/** Default sigops cost to reserve for coinbase transaction outputs when creating block templates. */ +static constexpr unsigned int DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS{400}; /** This accounts for the block header, var_int encoding of the transaction count and a minimally viable * coinbase transaction. It adds an additional safety margin, because even with a thorough understanding * of block serialization, it's easy to make a costly mistake when trying to squeeze every last byte. diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index c17ba5342be..ce1bd743b46 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1015,8 +1015,9 @@ static RPCHelpMan getblocktemplate() result.pushKV("signet_challenge", HexStr(consensusParams.signet_challenge)); } - if (!block_template->getCoinbaseCommitment().empty()) { - result.pushKV("default_witness_commitment", HexStr(block_template->getCoinbaseCommitment())); + if (auto coinbase{block_template->getCoinbaseTx()}; coinbase.required_outputs.size() > 0) { + CHECK_NONFATAL(coinbase.required_outputs.size() == 1); // Only one output is currently expected + result.pushKV("default_witness_commitment", HexStr(coinbase.required_outputs[0].scriptPubKey)); } return result; diff --git a/src/validation.cpp b/src/validation.cpp index 4b32ccf1a96..6e248c65525 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4026,9 +4026,8 @@ void ChainstateManager::UpdateUncommittedBlockStructures(CBlock& block, const CB } } -std::vector ChainstateManager::GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const +void ChainstateManager::GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const { - std::vector commitment; int commitpos = GetWitnessCommitmentIndex(block); std::vector ret(32, 0x00); if (commitpos == NO_WITNESS_COMMITMENT) { @@ -4044,13 +4043,11 @@ std::vector ChainstateManager::GenerateCoinbaseCommitment(CBlock& out.scriptPubKey[4] = 0xa9; out.scriptPubKey[5] = 0xed; memcpy(&out.scriptPubKey[6], witnessroot.begin(), 32); - commitment = std::vector(out.scriptPubKey.begin(), out.scriptPubKey.end()); CMutableTransaction tx(*block.vtx[0]); tx.vout.push_back(out); block.vtx[0] = MakeTransactionRef(std::move(tx)); } UpdateUncommittedBlockStructures(block, pindexPrev); - return commitment; } bool HasValidProofOfWork(std::span headers, const Consensus::Params& consensusParams) diff --git a/src/validation.h b/src/validation.h index 6617fe7affe..58df66e3c1b 100644 --- a/src/validation.h +++ b/src/validation.h @@ -1311,7 +1311,7 @@ public: void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const; /** Produce the necessary coinbase commitment for a block (modifies the hash, don't call for mined blocks). */ - std::vector GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const; + void GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const; /** This is used by net_processing to report pre-synchronization progress of headers, as * headers are not yet fed to validation during that time, but validation is (for now) diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py index f2c75f6e09f..2fc4497a426 100755 --- a/test/functional/interface_ipc.py +++ b/test/functional/interface_ipc.py @@ -68,9 +68,23 @@ class IPCInterfaceTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) + def run_deprecated_mining_test(self): + self.log.info("Running deprecated mining interface test") + async def async_routine(): + ctx, init = await make_capnp_init_ctx(self) + self.log.debug("Calling deprecated makeMiningOld2 should raise an error") + try: + await init.makeMiningOld2() + raise AssertionError("makeMiningOld2 unexpectedly succeeded") + except capnp.KjException as e: + assert_equal(e.description, "remote exception: std::exception: Old mining interface (@2) not supported. Please update your client!") + assert_equal(e.type, "FAILED") + asyncio.run(capnp.run(async_routine())) + def run_test(self): self.run_echo_test() self.run_mining_test() + self.run_deprecated_mining_test() if __name__ == '__main__': IPCInterfaceTest(__file__).main() diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 61c050e74fe..2221d462879 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -6,6 +6,7 @@ import asyncio from contextlib import AsyncExitStack from io import BytesIO +import re from test_framework.blocktools import NULL_OUTPOINT from test_framework.messages import ( MAX_BLOCK_WEIGHT, @@ -33,7 +34,6 @@ from test_framework.ipc_util import ( make_capnp_init_ctx, mining_get_block, mining_get_coinbase_tx, - mining_get_coinbase_raw_tx, mining_wait_next_template, wait_and_do, ) @@ -92,27 +92,12 @@ class IPCMiningTest(BitcoinTestFramework): coinbase_tx.vout[0].nValue = coinbase_res.blockRewardRemaining # Add SegWit OP_RETURN. This is currently always present even for # empty blocks, but this may change. - found_witness_op_return = False - # Compare SegWit OP_RETURN to getCoinbaseCommitment() - coinbase_commitment = (await template.getCoinbaseCommitment(ctx)).result for output_data in coinbase_res.requiredOutputs: output = CTxOut() output.deserialize(BytesIO(output_data)) coinbase_tx.vout.append(output) - if output.scriptPubKey == coinbase_commitment: - found_witness_op_return = True - - assert_equal(has_witness, found_witness_op_return) coinbase_tx.nLockTime = coinbase_res.lockTime - # Compare to dummy coinbase transaction provided by the deprecated - # getCoinbaseRawTx() - coinbase_legacy = await mining_get_coinbase_raw_tx(template, ctx) - assert_equal(coinbase_legacy.vout[0].nValue, coinbase_res.blockRewardRemaining) - # Swap dummy output for our own - coinbase_legacy.vout[0].scriptPubKey = coinbase_tx.vout[0].scriptPubKey - assert_equal(coinbase_tx.serialize().hex(), coinbase_legacy.serialize().hex()) - return coinbase_tx async def make_mining_ctx(self): @@ -242,9 +227,6 @@ class IPCMiningTest(BitcoinTestFramework): async with AsyncExitStack() as stack: opts = self.capnp_modules['mining'].BlockCreateOptions() - opts.useMempool = True - opts.blockReservedWeight = 4000 - opts.coinbaseOutputMaxAdditionalSigops = 0 template = await mining_create_block_template(mining, stack, ctx, opts) assert template is not None block = await mining_get_block(template, ctx) @@ -257,19 +239,28 @@ class IPCMiningTest(BitcoinTestFramework): empty_block = await mining_get_block(empty_template, ctx) assert_equal(len(empty_block.vtx), 1) - self.log.debug("Enforce minimum reserved weight for IPC clients too") - opts.blockReservedWeight = 0 - try: - await mining.createNewBlock(opts) - raise AssertionError("createNewBlock unexpectedly succeeded") - except capnp.lib.capnp.KjException as e: + self.log.debug("Enforce minimum reserved weight for IPC clients too") + opts.blockReservedWeight = 0 + try: + await mining.createNewBlock(ctx, opts) + raise AssertionError("createNewBlock unexpectedly succeeded") + except capnp.lib.capnp.KjException as e: + if e.type == "DISCONNECTED": + # The remote exception isn't caught currently and leads to a + # std::terminate call. Just detect and restart in this case. + # This bug is fixed with + # https://github.com/bitcoin-core/libmultiprocess/pull/218 + assert_equal(e.description, "Peer disconnected.") + self.nodes[0].wait_until_stopped(expected_ret_code=(-11, -6, 1, 66), expected_stderr=re.compile("")) + self.start_node(0) + else: assert_equal(e.description, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units") assert_equal(e.type, "FAILED") asyncio.run(capnp.run(async_routine())) def run_coinbase_and_submission_test(self): - """Test coinbase construction (getCoinbaseTx, getCoinbaseCommitment) and block submission (submitSolution).""" + """Test coinbase construction (getCoinbaseTx) and block submission (submitSolution).""" self.log.info("Running coinbase construction and submission test") async def async_routine(): @@ -278,7 +269,7 @@ class IPCMiningTest(BitcoinTestFramework): current_block_height = self.nodes[0].getchaintips()[0]["height"] check_opts = self.capnp_modules['mining'].BlockCheckOptions() - async with destroying((await mining.createNewBlock(self.default_block_create_options)).result, ctx) as template: + async with destroying((await mining.createNewBlock(ctx, self.default_block_create_options)).result, ctx) as template: block = await mining_get_block(template, ctx) balance = self.miniwallet.get_balance() coinbase = await self.build_coinbase_test(template, ctx, self.miniwallet) @@ -291,7 +282,7 @@ class IPCMiningTest(BitcoinTestFramework): self.log.debug("Submit a block with a bad version") block.nVersion = 0 block.solve() - check = await mining.checkBlock(block.serialize(), check_opts) + check = await mining.checkBlock(ctx, block.serialize(), check_opts) assert_equal(check.result, False) assert_equal(check.reason, "bad-version(0x00000000)") submitted = (await template.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())).result @@ -301,7 +292,7 @@ class IPCMiningTest(BitcoinTestFramework): block.solve() self.log.debug("First call checkBlock()") - block_valid = (await mining.checkBlock(block.serialize(), check_opts)).result + block_valid = (await mining.checkBlock(ctx, block.serialize(), check_opts)).result assert_equal(block_valid, True) # The remote template block will be mutated, capture the original: @@ -333,7 +324,7 @@ class IPCMiningTest(BitcoinTestFramework): self.miniwallet.rescan_utxos() assert_equal(self.miniwallet.get_balance(), balance + 1) self.log.debug("Check block should fail now, since it is a duplicate") - check = await mining.checkBlock(block.serialize(), check_opts) + check = await mining.checkBlock(ctx, block.serialize(), check_opts) assert_equal(check.result, False) assert_equal(check.reason, "inconclusive-not-best-prevblk") @@ -341,12 +332,7 @@ class IPCMiningTest(BitcoinTestFramework): def run_test(self): self.miniwallet = MiniWallet(self.nodes[0]) - self.default_block_create_options = self.capnp_modules['mining'].BlockCreateOptions( - useMempool=True, - blockReservedWeight=4000, - coinbaseOutputMaxAdditionalSigops=0 - ) - + self.default_block_create_options = self.capnp_modules['mining'].BlockCreateOptions() self.run_mining_interface_test() self.run_block_template_test() self.run_coinbase_and_submission_test() diff --git a/test/functional/test_framework/ipc_util.py b/test/functional/test_framework/ipc_util.py index 1b1ad7a55aa..11497463eb3 100644 --- a/test/functional/test_framework/ipc_util.py +++ b/test/functional/test_framework/ipc_util.py @@ -12,7 +12,7 @@ from pathlib import Path import shutil from typing import Optional -from test_framework.messages import CBlock, CTransaction +from test_framework.messages import CBlock # Test may be skipped and not have capnp installed try: @@ -108,7 +108,7 @@ async def make_capnp_init_ctx(self): async def mining_create_block_template(mining, stack, ctx, opts): """Call mining.createNewBlock() and return template, then call template.destroy() when stack exits.""" - response = await mining.createNewBlock(opts) + response = await mining.createNewBlock(ctx, opts) if not response._has("result"): return None return await stack.enter_async_context(destroying(response.result, ctx)) @@ -129,14 +129,6 @@ async def mining_get_block(block_template, ctx): return block -async def mining_get_coinbase_raw_tx(block_template, ctx): - assert block_template is not None - coinbase_data = BytesIO((await block_template.getCoinbaseRawTx(ctx)).result) - tx = CTransaction() - tx.deserialize(coinbase_data) - return tx - - async def mining_get_coinbase_tx(block_template, ctx) -> CoinbaseTxData: assert block_template is not None # Note: the template_capnp struct will be garbage-collected when this diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 15686dcde19..9f9ad33c451 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -22,6 +22,7 @@ import collections import shlex import shutil import sys +from collections.abc import Iterable from pathlib import Path from .authproxy import ( @@ -469,6 +470,12 @@ class TestNode(): """Checks whether the node has stopped. Returns True if the node has stopped. False otherwise. + + If the process has exited, asserts that the exit code matches + `expected_ret_code` (which may be a single value or an iterable of values), + and that stderr matches `expected_stderr` exactly or, if a regex pattern is + provided, contains the pattern. + This method is responsible for freeing resources (self.process).""" if not self.running: return True @@ -477,12 +484,17 @@ class TestNode(): return False # process has stopped. Assert that it didn't return an error code. - assert return_code == expected_ret_code, self._node_msg( + if not isinstance(expected_ret_code, Iterable): + expected_ret_code = (expected_ret_code,) + assert return_code in expected_ret_code, self._node_msg( f"Node returned unexpected exit code ({return_code}) vs ({expected_ret_code}) when stopping") # Check that stderr is as expected self.stderr.seek(0) stderr = self.stderr.read().decode('utf-8').strip() - if stderr != expected_stderr: + if isinstance(expected_stderr, re.Pattern): + if not expected_stderr.search(stderr): + raise AssertionError(f"Unexpected stderr {stderr!r} does not contain {expected_stderr.pattern!r}") + elif stderr != expected_stderr: raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) self.stdout.close()