Merge bitcoin/bitcoin#32290: test: allow all functional tests to be run or skipped with --usecli

666016e56b ci: use --usecli in one of the CI jobs (Martin Zumsande)
7ea248a020 test: Disable several (sub)tests with cli (Martin Zumsande)
f420b6356b test: skip subtests that check for wrong types with cli (Martin Zumsande)
6530d0015b test: add function to convert to json for height_or_hash params (Martin Zumsande)
54d28722ba test: Don't send empty named args with cli (Martin Zumsande)
cca422060e test: convert tuple to json for cli (Martin Zumsande)
af34e98086 test: make rpc_psbt.py usable with --usecli (Martin Zumsande)
8f8ce9e174 test: rename .rpc to ._rpc and remove unnecessary uses (Martin Zumsande)
5b08885986 test: enable functional tests with large rpc args for cli (Martin Zumsande)
7d5352ac73 test: use -stdin for large rpc commands (Martin Zumsande)
6c364e0c10 test: Enable various tests for usage with cli (Martin Zumsande)

Pull request description:

  Fixes #32264

  I looked into all current failures listed in the issue, as well all tests that are already disabled for the cli with `self.supports_cli = False`. There are several reasons why existing tests fail  with `--usecli` on many systems, the most important ones are:

  - Most common reason is that the test executes a RPC call with a large arg that exceeds `MAX_ARG_STRLEN` of the OS, which is usually 128kb on linux: This is fixed by using `-stdin` for these large calls (idea by 0xB10C)
  - they test specifically the rpc interface - nothing to do there except disabling.
  - Some functional test submit wrong types to params on purpose to test the error message (which is different when using the cli) - deactivated these specific subtests locally for the cli when there is just one or two of them, deactivated the entire tests when there are more spots
  - When python sets `None` for an arg, the cli converts this to 'null' in `arg_to_cli`. This is fine e.g. for boolean args, but doesn't work for strings where it's interpreted as the string 'null'. Bypass this for named args by not including args in case the value is `None` for the cli is used (it's effectively the same as leaving the optional arg out).
  -  the `height_or_hash` param used in some RPC needs to be converted to a JSON (effectively adding full quotes).
  - Some tests were marked with `self.supports_cli = False` in the past but run fine on master today - enabled those.

  In total, this PR fixes all tests that fail on master and reduces the number of tests that are deactivated (`self.supports_cli = False`) from 40 to 21.
  It also adds `--usecli` to one CI job (multiprocess, i686, DEBUG) to detect regressions.

ACKs for top commit:
  maflcko:
    re-ACK 666016e56b 🔀
  pinheadmz:
    re-ACK 666016e56b

Tree-SHA512: 7a1efd212649ca100b236a1239294d40ecd36e2720e3b173a230b14545bb40b135111db7fed8a0d1448120f5387da146a03f1912e2028c8d03a0b6a3ca8761b0
This commit is contained in:
merge-script 2025-07-03 10:20:03 +01:00
commit 6251949443
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
42 changed files with 110 additions and 98 deletions

View File

@ -13,7 +13,7 @@ export CI_IMAGE_PLATFORM="linux/amd64"
export PACKAGES="llvm clang g++-multilib"
export DEP_OPTS="DEBUG=1 MULTIPROCESS=1"
export GOAL="install"
export TEST_RUNNER_EXTRA="--v2transport"
export TEST_RUNNER_EXTRA="--v2transport --usecli"
export BITCOIN_CONFIG="\
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER='clang;-m32' \

View File

@ -481,7 +481,7 @@ class AssumeutxoTest(BitcoinTestFramework):
# Use a hash instead of a height
prev_snap_hash = n0.getblockhash(prev_snap_height)
dump_output5 = n0.dumptxoutset('utxos5.dat', rollback=prev_snap_hash)
dump_output5 = n0.dumptxoutset('utxos5.dat', rollback=self.convert_to_json_for_cli(prev_snap_hash))
assert_equal(sha256sum_file(dump_output4['path']), sha256sum_file(dump_output5['path']))
# Ensure n0 is back at the tip

View File

@ -41,7 +41,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.supports_cli = False
self.extra_args = [
[],
["-coinstatsindex"]
@ -104,7 +103,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_equal(res0, res2)
# Fetch old stats by hash
res3 = index_node.gettxoutsetinfo(hash_option, res0['bestblock'])
res3 = index_node.gettxoutsetinfo(hash_option, self.convert_to_json_for_cli(res0['bestblock']))
del res3['block_info'], res3['total_unspendable_amount']
res3.pop('muhash', None)
assert_equal(res0, res3)
@ -243,7 +242,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_equal(res12, res10)
self.log.info("Test obtaining info for a non-existent block hash")
assert_raises_rpc_error(-5, "Block not found", index_node.gettxoutsetinfo, hash_type="none", hash_or_height="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", use_index=True)
assert_raises_rpc_error(-5, "Block not found", index_node.gettxoutsetinfo, hash_type="none", hash_or_height=self.convert_to_json_for_cli("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), use_index=True)
def _test_use_index_option(self):
self.log.info("Test use_index option for nodes running the index")
@ -278,7 +277,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_not_equal(res["muhash"], res_invalid["muhash"])
# Test that requesting reorged out block by hash is still returning correct results
res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=reorg_block)
res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=self.convert_to_json_for_cli(reorg_block))
assert_equal(res_invalid2["muhash"], res_invalid["muhash"])
assert_not_equal(res["muhash"], res_invalid2["muhash"])

View File

@ -100,7 +100,6 @@ class BIP68_112_113Test(BitcoinTestFramework):
self.extra_args = [[
f'-testactivationheight=csv@{CSV_ACTIVATION_HEIGHT}',
]]
self.supports_cli = False
def create_self_transfer_from_utxo(self, input_tx):
utxo = self.miniwallet.get_utxo(txid=input_tx.txid_hex, mark_as_spent=False)

View File

@ -67,7 +67,7 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
for node in stats_nodes:
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=self.convert_to_json_for_cli(tip))['muhash']
self.generate(self.nodes[0], 500)
self.sync_index(height=700)
@ -85,14 +85,14 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
for node in stats_nodes:
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=self.convert_to_json_for_cli(tip))['muhash']
self.log.info("check if we can access the blockfilter and coinstats of a pruned block")
height_hash = self.nodes[0].getblockhash(2)
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(height_hash)['filter']), 0)
for node in stats_nodes:
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash']
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=self.convert_to_json_for_cli(height_hash))['muhash']
# mine and sync index up to a height that will later be the pruneheight
self.generate(self.nodes[0], 51)
@ -106,7 +106,7 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, msg, node.getblockfilter, height_hash)
for node in stats_nodes:
msg = "Querying specific block heights requires coinstatsindex"
assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", height_hash)
assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", self.convert_to_json_for_cli(height_hash))
self.generate(self.nodes[0], 749)

View File

@ -52,7 +52,6 @@ class MaxUploadTest(BitcoinTestFramework):
self.extra_args = [[
f"-maxuploadtarget={UPLOAD_TARGET_MB}M",
]]
self.supports_cli = False
def assert_uploadtarget_state(self, *, target_reached, serve_historical_blocks):
"""Verify the node's current upload target state via the `getnettotals` RPC call."""

View File

@ -69,7 +69,6 @@ class PruneTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 6
self.supports_cli = False
self.uses_wallet = None
# Create nodes 0 and 1 to mine.

View File

@ -33,7 +33,6 @@ class ReplaceByFeeTest(BitcoinTestFramework):
[
],
]
self.supports_cli = False
self.uses_wallet = None
def run_test(self):

View File

@ -31,7 +31,6 @@ class MempoolLimitTest(BitcoinTestFramework):
self.extra_args = [[
"-maxmempool=5",
]]
self.supports_cli = False
def test_rbf_carveout_disallowed(self):
node = self.nodes[0]

View File

@ -65,7 +65,6 @@ class MiningTest(BitcoinTestFramework):
["-fastprune", "-prune=1"]
]
self.setup_clean_chain = True
self.supports_cli = False
def mine_chain(self):
self.log.info('Create some old blocks')

View File

@ -43,7 +43,6 @@ class PackageRelayTest(BitcoinTestFramework):
self.extra_args = [[
"-maxmempool=5",
]] * self.num_nodes
self.supports_cli = False
def raise_network_minfee(self):
fill_mempool(self, self.nodes[0])

View File

@ -15,7 +15,6 @@ from test_framework.util import (
class DisconnectBanTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.supports_cli = False
def run_test(self):
self.log.info("Connect nodes both ways")

View File

@ -61,7 +61,6 @@ class PackageRelayTest(BitcoinTestFramework):
self.extra_args = [[
"-maxmempool=5",
]]
self.supports_cli = False
def create_tx_below_mempoolminfee(self, wallet):
"""Create a 1-input 1sat/vB transaction using a confirmed UTXO. Decrement and use

View File

@ -221,7 +221,6 @@ class SegWitTest(BitcoinTestFramework):
["-acceptnonstdtxn=1", f"-testactivationheight=segwit@{SEGWIT_HEIGHT}", "-par=1"],
["-acceptnonstdtxn=0", f"-testactivationheight=segwit@{SEGWIT_HEIGHT}"],
]
self.supports_cli = False
# Helper functions

View File

@ -28,7 +28,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.supports_cli = False
def create_keys(self, num_keys):
self.pub = []

View File

@ -19,7 +19,7 @@ class DeprecatedRpcTest(BitcoinTestFramework):
# such RPCs are fully removed. For example:
#
# self.log.info("Test generate RPC")
# assert_raises_rpc_error(-32, 'The wallet generate rpc method is deprecated', self.nodes[0].rpc.generate, 1)
# assert_raises_rpc_error(-32, 'The wallet generate rpc method is deprecated', self.nodes[0].generate, 1)
#
# Please ensure that for all the RPC methods tested here, there is
# at least one other functional test that still tests the RPCs
@ -32,7 +32,7 @@ class DeprecatedRpcTest(BitcoinTestFramework):
self.log.info("Tests for deprecated wallet-related RPC methods (if any)")
self.log.info("Test settxfee RPC deprecation")
self.nodes[0].createwallet("settxfeerpc")
assert_raises_rpc_error(-32, 'settxfee is deprecated and will be fully removed in v31.0.', self.nodes[0].rpc.settxfee, 0.01)
assert_raises_rpc_error(-32, 'settxfee is deprecated and will be fully removed in v31.0.', self.nodes[0].settxfee, 0.01)
if __name__ == '__main__':
DeprecatedRpcTest(__file__).main()

View File

@ -21,17 +21,17 @@ class EstimateFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee)
assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee)
# wrong type for conf_target
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimatesmartfee, 'foo')
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 'foo')
# cli handles wrong types differently
if not self.options.usecli:
# wrong type for conf_target
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimatesmartfee, 'foo')
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 'foo')
# wrong type for estimatesmartfee(estimate_mode)
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].estimatesmartfee, 1, 1)
# wrong type for estimaterawfee(threshold)
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo')
# wrong type for estimatesmartfee(estimate_mode)
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].estimatesmartfee, 1, 1)
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo')
# wrong type for estimaterawfee(threshold)
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo')
# extra params
assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1)
assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee, 1, 1, 1)

View File

@ -124,11 +124,12 @@ class RPCGenerateTest(BitcoinTestFramework):
"cli option. Refer to -help for more information.\n"
)
self.log.info("Test rpc generate raises with message to use cli option")
assert_raises_rpc_error(-32601, message, self.nodes[0].rpc.generate)
if not self.options.usecli:
self.log.info("Test rpc generate raises with message to use cli option")
assert_raises_rpc_error(-32601, message, self.nodes[0]._rpc.generate)
self.log.info("Test rpc generate help prints message to use cli option")
assert_equal(message, self.nodes[0].help("generate"))
self.log.info("Test rpc generate help prints message to use cli option")
assert_equal(message, self.nodes[0].help("generate"))
self.log.info("Test rpc generate is a hidden command not discoverable in general help")
assert message not in self.nodes[0].help()

View File

@ -67,8 +67,11 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Arguments must be valid")
assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", peer_0_peer_1_id)
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id)
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0")
# cli handles wrong types differently
if not self.options.usecli:
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id)
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0")
self.log.info("We must already have the header")
assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0)

View File

@ -36,7 +36,6 @@ class GetblockstatsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.supports_cli = False
def get_stats(self):
return [self.nodes[0].getblockstats(hash_or_height=self.start_height + i) for i in range(self.max_stat_pos+1)]
@ -120,7 +119,7 @@ class GetblockstatsTest(BitcoinTestFramework):
# Check selecting block by hash too
blockhash = self.expected_stats[i]['blockhash']
stats_by_hash = self.nodes[0].getblockstats(hash_or_height=blockhash)
stats_by_hash = self.nodes[0].getblockstats(hash_or_height=self.convert_to_json_for_cli(blockhash))
assert_equal(stats_by_hash, self.expected_stats[i])
# Make sure each stat can be queried on its own
@ -162,10 +161,10 @@ class GetblockstatsTest(BitcoinTestFramework):
self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee', f'aaa{inv_sel_stat}'])
# Mainchain's genesis block shouldn't be found on regtest
assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats,
hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f')
hash_or_height=self.convert_to_json_for_cli('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'))
# Invalid number of args
assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, '00', 1, 2)
assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, self.convert_to_json_for_cli('00'), 1, 2)
assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats)
self.log.info('Test block height 0')
@ -186,7 +185,7 @@ class GetblockstatsTest(BitcoinTestFramework):
self.log.info("Test when only header is known")
block = self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False)
self.nodes[0].submitheader(block["hex"])
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: self.nodes[0].getblockstats(block['hash']))
assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: self.nodes[0].getblockstats(self.convert_to_json_for_cli(block['hash'])))
self.log.info('Test when block is missing')
(self.nodes[0].blocks_path / 'blk00000.dat').rename(self.nodes[0].blocks_path / 'blk00000.dat.backup')

View File

@ -35,7 +35,9 @@ class DescriptorTest(BitcoinTestFramework):
def run_test(self):
assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo)
assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1)
# cli handles wrong types differently
if not self.options.usecli:
assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1)
assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "")
assert_raises_rpc_error(-5, "pk(): Key ' 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, "pk( 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)")
assert_raises_rpc_error(-5, "pk(): Key '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 ' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 )")

View File

@ -45,7 +45,6 @@ def process_mapping(fname):
class HelpRpcTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.supports_cli = False
self.uses_wallet = None
def run_test(self):
@ -93,7 +92,8 @@ class HelpRpcTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, 'help', node.help, 'foo', 'bar')
# invalid argument
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0)
if not self.options.usecli:
assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0)
# help of unknown command
assert_equal(node.help('foo'), 'help: unknown command: foo')

View File

@ -97,10 +97,12 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
node = self.nodes[0]
# Missing arg returns the help text
assert_raises_rpc_error(-1, "Return information about the given bitcoin address.", node.validateaddress)
# Explicit None is not allowed for required parameters
assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", node.validateaddress, None)
if not self.options.usecli:
# Missing arg returns the help text
assert_raises_rpc_error(-1, "Return information about the given bitcoin address.", node.validateaddress)
# Explicit None is not allowed for required parameters
assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", node.validateaddress, None)
def test_getaddressinfo(self):
node = self.nodes[0]

View File

@ -19,7 +19,6 @@ from test_framework.authproxy import JSONRPCException
class RpcMiscTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.supports_cli = False
def run_test(self):
node = self.nodes[0]

View File

@ -36,7 +36,6 @@ class PreciousTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.supports_cli = False
def setup_network(self):
self.setup_nodes()

View File

@ -72,7 +72,6 @@ class PSBTTest(BitcoinTestFramework):
# whitelist peers to speed up tx relay / mempool sync
for args in self.extra_args:
args.append("-whitelist=noban@127.0.0.1")
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@ -96,7 +95,7 @@ class PSBTTest(BitcoinTestFramework):
signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)
# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
signed_psbt_incomplete = wallet.walletprocesspsbt(psbt=signed_psbt_obj.to_base64(), finalize=False)
assert signed_psbt_incomplete["complete"] is False
def test_utxo_conversion(self):
@ -1209,7 +1208,8 @@ class PSBTTest(BitcoinTestFramework):
self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed")
assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all")
self.test_sighash_mismatch()
if not self.options.usecli:
self.test_sighash_mismatch()
self.test_sighash_adding()
if __name__ == '__main__':

View File

@ -33,6 +33,7 @@ class RPCWhitelistTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.supports_cli = False
def run_test(self):
# 0 => Username

View File

@ -8,6 +8,7 @@ import configparser
from enum import Enum
import argparse
from datetime import datetime, timezone
import json
import logging
import os
import platform
@ -595,7 +596,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
node.wait_for_rpc_connection()
if self.options.coveragedir is not None:
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
coverage.write_all_rpc_commands(self.options.coveragedir, node._rpc)
def start_nodes(self, extra_args=None, *args, **kwargs):
"""Start multiple bitcoinds"""
@ -610,7 +611,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
if self.options.coveragedir is not None:
for node in self.nodes:
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
coverage.write_all_rpc_commands(self.options.coveragedir, node._rpc)
def stop_node(self, i, expected_stderr='', wait=0):
"""Stop a bitcoind test node"""
@ -1083,3 +1084,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def has_blockfile(self, node, filenum: str):
return (node.blocks_path/ f"blk{filenum}.dat").is_file()
def convert_to_json_for_cli(self, text):
if self.options.usecli:
return json.dumps(text)
return text

View File

@ -46,6 +46,8 @@ BITCOIND_PROC_WAIT_TIMEOUT = 60
# The size of the blocks xor key
# from InitBlocksdirXorKey::xor_key.size()
NUM_XOR_BYTES = 8
CLI_MAX_ARG_SIZE = 131071 # many systems have a 128kb limit per arg (MAX_ARG_STRLEN)
# The null blocks key (all 0s)
NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES)
BITCOIN_PID_FILENAME_DEFAULT = "bitcoind.pid"
@ -153,7 +155,7 @@ class TestNode():
self.running = False
self.process = None
self.rpc_connected = False
self.rpc = None
self._rpc = None # Should usually not be accessed directly in tests to allow for --usecli mode
self.reuse_http_connections = True # Must be set before calling get_rpc_proxy() i.e. before restarting node
self.url = None
self.log = logging.getLogger('TestFramework.node%d' % i)
@ -210,8 +212,8 @@ class TestNode():
if self.use_cli:
return getattr(self.cli, name)
else:
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
return getattr(self.rpc, name)
assert self.rpc_connected and self._rpc is not None, self._node_msg("Error: no RPC connection")
return getattr(self._rpc, name)
def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, env=None, **kwargs):
"""Start the node."""
@ -315,8 +317,8 @@ class TestNode():
self.rpc_connected = True
if self.use_cli:
return
self.rpc = rpc
self.url = self.rpc.rpc_url
self._rpc = rpc
self.url = self._rpc.rpc_url
return
except JSONRPCException as e:
# Suppress these as they are expected during initialization.
@ -393,9 +395,9 @@ class TestNode():
if self.use_cli:
return self.cli("-rpcwallet={}".format(wallet_name))
else:
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
assert self.rpc_connected and self._rpc, self._node_msg("RPC not connected")
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
return self.rpc / wallet_path
return self._rpc / wallet_path
def version_is_at_least(self, ver):
return self.version is None or self.version >= ver
@ -450,7 +452,7 @@ class TestNode():
self.running = False
self.process = None
self.rpc_connected = False
self.rpc = None
self._rpc = None
self.log.debug("Node stopped")
return True
@ -879,7 +881,7 @@ def arg_to_cli(arg):
return str(arg).lower()
elif arg is None:
return 'null'
elif isinstance(arg, dict) or isinstance(arg, list):
elif isinstance(arg, dict) or isinstance(arg, list) or isinstance(arg, tuple):
return json.dumps(arg, default=serialization_fallback)
else:
return str(arg)
@ -916,16 +918,28 @@ class TestNodeCLI():
def send_cli(self, clicommand=None, *args, **kwargs):
"""Run bitcoin-cli command. Deserializes returned string as python object."""
pos_args = [arg_to_cli(arg) for arg in args]
named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()]
named_args = [key + "=" + arg_to_cli(value) for (key, value) in kwargs.items() if value is not None]
p_args = self.binaries.rpc_argv() + [f"-datadir={self.datadir}"] + self.options
if named_args:
p_args += ["-named"]
base_arg_pos = len(p_args)
if clicommand is not None:
p_args += [clicommand]
p_args += pos_args + named_args
max_arg_size = max(len(arg) for arg in p_args)
stdin_data = self.input
if max_arg_size > CLI_MAX_ARG_SIZE:
self.log.debug(f"Cli: Command size {max_arg_size} too large, using stdin")
rpc_args = "\n".join([arg for arg in p_args[base_arg_pos:]])
if stdin_data is not None:
stdin_data += "\n" + rpc_args
else:
stdin_data = rpc_args
p_args = p_args[:base_arg_pos] + ['-stdin']
self.log.debug("Running bitcoin-cli {}".format(p_args[2:]))
process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
cli_stdout, cli_stderr = process.communicate(input=self.input)
cli_stdout, cli_stderr = process.communicate(input=stdin_data)
returncode = process.poll()
if returncode:
match = re.match(r'error code: ([-0-9]+)\nerror message:\n(.*)', cli_stderr)

View File

@ -78,7 +78,6 @@ class AddressTypeTest(BitcoinTestFramework):
]
# whitelist peers to speed up tx relay / mempool sync
self.noban_tx_relay = True
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

View File

@ -327,7 +327,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
for node in descriptors_nodes:
self.log.info(f"- {node.version}")
wallet_name = f"up_{node.version}"
node.rpc.createwallet(wallet_name=wallet_name, descriptors=True)
node.createwallet(wallet_name=wallet_name, descriptors=True)
wallet_prev = node.get_wallet_rpc(wallet_name)
address = wallet_prev.getnewaddress('', "bech32")
addr_info = wallet_prev.getaddressinfo(address)
@ -395,9 +395,9 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
self.log.info(f"- {node.version}")
wallet_name = f"legacy_up_{node.version}"
if self.major_version_at_least(node, 21):
node.rpc.createwallet(wallet_name=wallet_name, descriptors=False)
node.createwallet(wallet_name=wallet_name, descriptors=False)
else:
node.rpc.createwallet(wallet_name=wallet_name)
node.createwallet(wallet_name=wallet_name)
wallet_prev = node.get_wallet_rpc(wallet_name)
address = wallet_prev.getnewaddress('', "bech32")
addr_info = wallet_prev.getaddressinfo(address)

View File

@ -453,14 +453,14 @@ class WalletTest(BitcoinTestFramework):
# - True: unicode escaped as \u....
# - False: unicode directly as UTF-8
for mode in [True, False]:
self.nodes[0].rpc.ensure_ascii = mode
self.nodes[0]._rpc.ensure_ascii = mode
# unicode check: Basic Multilingual Plane, Supplementary Plane respectively
for label in [u'рыба', u'𝅘𝅥𝅯']:
addr = self.nodes[0].getnewaddress()
self.nodes[0].setlabel(addr, label)
test_address(self.nodes[0], addr, labels=[label])
assert label in self.nodes[0].listlabels()
self.nodes[0].rpc.ensure_ascii = True # restore to default
self.nodes[0]._rpc.ensure_ascii = True # restore to default
# -reindex tests
chainlimit = 6

View File

@ -139,8 +139,9 @@ class BumpFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, msg, rbf_node.bumpfee, rbfid, fee_rate=zero_value)
msg = "Invalid amount"
# Test fee_rate values that don't pass fixed-point parsing checks.
for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value)
if not self.options.usecli:
for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value)
# Test fee_rate values that cannot be represented in sat/vB.
for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]:
assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value)
@ -164,9 +165,10 @@ class BumpFeeTest(BitcoinTestFramework):
rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})
self.log.info("Test invalid estimate_mode settings")
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string",
rbf_node.bumpfee, rbfid, estimate_mode=v)
if not self.options.usecli:
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string",
rbf_node.bumpfee, rbfid, estimate_mode=v)
for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]:
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
rbf_node.bumpfee, rbfid, estimate_mode=mode)

View File

@ -90,17 +90,18 @@ class WalletEncryptionTest(BitcoinTestFramework):
assert_equal(actual_time, expected_time)
self.nodes[0].walletlock()
# Test passphrase with null characters
passphrase_with_nulls = "Phrase\0With\0Nulls"
self.nodes[0].walletpassphrasechange(passphrase2, passphrase_with_nulls)
# walletpassphrasechange should not stop at null characters
assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase_with_nulls.partition("\0")[0], 10)
assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", self.nodes[0].walletpassphrasechange, passphrase_with_nulls.partition("\0")[0], "abc")
assert_raises_rpc_error(-14, "wallet passphrase entered is incorrect. It contains a null character (ie - a zero byte)", self.nodes[0].walletpassphrase, passphrase_with_nulls + "\0", 10)
assert_raises_rpc_error(-14, "The old wallet passphrase entered is incorrect. It contains a null character (ie - a zero byte)", self.nodes[0].walletpassphrasechange, passphrase_with_nulls + "\0", "abc")
with WalletUnlock(self.nodes[0], passphrase_with_nulls):
sig = self.nodes[0].signmessage(address, msg)
assert self.nodes[0].verifymessage(address, sig, msg)
if not self.options.usecli: # can't be done with the test framework for cli since subprocess.Popen doesn't allow null characters
# Test passphrase with null characters
passphrase_with_nulls = "Phrase\0With\0Nulls"
self.nodes[0].walletpassphrasechange(passphrase2, passphrase_with_nulls)
# walletpassphrasechange should not stop at null characters
assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase_with_nulls.partition("\0")[0], 10)
assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", self.nodes[0].walletpassphrasechange, passphrase_with_nulls.partition("\0")[0], "abc")
assert_raises_rpc_error(-14, "wallet passphrase entered is incorrect. It contains a null character (ie - a zero byte)", self.nodes[0].walletpassphrase, passphrase_with_nulls + "\0", 10)
assert_raises_rpc_error(-14, "The old wallet passphrase entered is incorrect. It contains a null character (ie - a zero byte)", self.nodes[0].walletpassphrasechange, passphrase_with_nulls + "\0", "abc")
with WalletUnlock(self.nodes[0], passphrase_with_nulls):
sig = self.nodes[0].signmessage(address, msg)
assert self.nodes[0].verifymessage(address, sig, msg)
self.log.info("Test that wallets without private keys cannot be encrypted")
self.nodes[0].createwallet(wallet_name="noprivs", disable_private_keys=True)

View File

@ -49,6 +49,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# whitelist peers to speed up tx relay / mempool sync
self.noban_tx_relay = True
self.rpc_timeout = 90 # to prevent timeouts in `test_transaction_too_large`
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

View File

@ -22,8 +22,6 @@ class WalletHDTest(BitcoinTestFramework):
# whitelist peers to speed up tx relay / mempool sync
self.noban_tx_relay = True
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

View File

@ -297,14 +297,14 @@ class WalletMiniscriptTest(BitcoinTestFramework):
psbt.i[0].map[k] = bytes.fromhex(preimage)
psbt = psbt.to_base64()
res = self.ms_sig_wallet.walletprocesspsbt(psbt=psbt, finalize=False)
psbtin = self.nodes[0].rpc.decodepsbt(res["psbt"])["inputs"][0]
psbtin = self.nodes[0].decodepsbt(res["psbt"])["inputs"][0]
sigs_field_name = "taproot_script_path_sigs" if is_taproot else "partial_signatures"
assert len(psbtin[sigs_field_name]) == sigs_count
res = self.ms_sig_wallet.finalizepsbt(res["psbt"])
assert res["complete"] == (stack_size is not None)
if stack_size is not None:
txin = self.nodes[0].rpc.decoderawtransaction(res["hex"])["vin"][0]
txin = self.nodes[0].decoderawtransaction(res["hex"])["vin"][0]
assert len(txin["txinwitness"]) == stack_size, txin["txinwitness"]
self.log.info("Broadcasting the transaction.")
# If necessary, satisfy a relative timelock

View File

@ -29,6 +29,7 @@ class WalletSendTest(BitcoinTestFramework):
self.num_nodes = 2
# whitelist peers to speed up tx relay / mempool sync
self.noban_tx_relay = True
self.supports_cli = False
self.extra_args = [
["-walletrbf=1"],
["-walletrbf=1"]

View File

@ -16,7 +16,6 @@ class WalletStartupTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.supports_cli = True
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

View File

@ -191,7 +191,6 @@ class WalletTaprootTest(BitcoinTestFramework):
self.num_nodes = 2
self.setup_clean_chain = True
self.extra_args = [['-keypool=100'], ['-keypool=100']]
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

View File

@ -17,7 +17,6 @@ from test_framework.messages import (
class TxnMallTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.supports_cli = False
self.extra_args = [[
"-deprecatedrpc=settxfee"
] for i in range(self.num_nodes)]

View File

@ -14,7 +14,6 @@ from test_framework.util import (
class TxnMallTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()