aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rw-r--r--test/functional/.gitignore1
-rwxr-xr-xtest/functional/feature_assumeutxo.py166
-rwxr-xr-xtest/functional/feature_assumevalid.py4
-rwxr-xr-xtest/functional/feature_blocksxor.py13
-rwxr-xr-xtest/functional/feature_config_args.py7
-rwxr-xr-xtest/functional/feature_fee_estimation.py4
-rwxr-xr-xtest/functional/feature_minchainwork.py2
-rwxr-xr-xtest/functional/feature_reindex.py3
-rwxr-xr-xtest/functional/feature_settings.py23
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py3
-rwxr-xr-xtest/functional/interface_usdt_coinselection.py10
-rwxr-xr-xtest/functional/mempool_package_rbf.py22
-rwxr-xr-xtest/functional/mempool_sigoplimit.py2
-rwxr-xr-xtest/functional/mining_basic.py9
-rwxr-xr-xtest/functional/p2p_1p1c_network.py2
-rwxr-xr-xtest/functional/p2p_headers_sync_with_minchainwork.py10
-rwxr-xr-xtest/functional/p2p_ibd_stalling.py3
-rwxr-xr-xtest/functional/p2p_node_network_limited.py4
-rwxr-xr-xtest/functional/p2p_permissions.py5
-rwxr-xr-xtest/functional/p2p_seednode.py55
-rwxr-xr-xtest/functional/p2p_tx_download.py4
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py8
-rwxr-xr-xtest/functional/rpc_blockchain.py61
-rwxr-xr-xtest/functional/rpc_createmultisig.py4
-rwxr-xr-xtest/functional/rpc_deriveaddresses.py3
-rwxr-xr-xtest/functional/rpc_dumptxoutset.py31
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py2
-rwxr-xr-xtest/functional/rpc_getblockstats.py13
-rwxr-xr-xtest/functional/rpc_getdescriptorinfo.py14
-rwxr-xr-xtest/functional/rpc_scantxoutset.py1
-rwxr-xr-xtest/functional/rpc_signrawtransactionwithkey.py33
-rwxr-xr-xtest/functional/rpc_txoutproof.py4
-rwxr-xr-xtest/functional/rpc_users.py21
-rw-r--r--test/functional/test_framework/blocktools.py4
-rwxr-xr-xtest/functional/test_framework/test_node.py13
-rw-r--r--test/functional/test_framework/test_shell.py10
-rw-r--r--test/functional/test_framework/util.py17
-rwxr-xr-xtest/functional/test_runner.py27
-rwxr-xr-xtest/functional/tool_signet_miner.py1
-rwxr-xr-xtest/functional/wallet_assumeutxo.py2
-rwxr-xr-xtest/functional/wallet_importdescriptors.py52
-rwxr-xr-xtest/functional/wallet_importmulti.py37
-rwxr-xr-xtest/functional/wallet_upgradewallet.py10
43 files changed, 593 insertions, 127 deletions
diff --git a/test/functional/.gitignore b/test/functional/.gitignore
index cb41d94423..0d20b6487c 100644
--- a/test/functional/.gitignore
+++ b/test/functional/.gitignore
@@ -1,2 +1 @@
*.pyc
-cache
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index a212704311..2995ece42f 100755
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -9,6 +9,7 @@ to a hash that has been compiled into bitcoind.
The assumeutxo value generated and used here is committed to in
`CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`.
"""
+import time
from shutil import rmtree
from dataclasses import dataclass
@@ -16,12 +17,22 @@ from test_framework.blocktools import (
create_block,
create_coinbase
)
-from test_framework.messages import tx_from_hex
+from test_framework.messages import (
+ CBlockHeader,
+ from_hex,
+ msg_headers,
+ tx_from_hex
+)
+from test_framework.p2p import (
+ P2PInterface,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
assert_equal,
assert_raises_rpc_error,
+ sha256sum_file,
+ try_rpc,
)
from test_framework.wallet import (
getnewdestination,
@@ -247,6 +258,74 @@ class AssumeutxoTest(BitcoinTestFramework):
node1.submitheader(main_block1)
node1.submitheader(main_block2)
+ def test_sync_from_assumeutxo_node(self, snapshot):
+ """
+ This test verifies that:
+ 1. An IBD node can sync headers from an AssumeUTXO node at any time.
+ 2. IBD nodes do not request historical blocks from AssumeUTXO nodes while they are syncing the background-chain.
+ 3. The assumeUTXO node dynamically adjusts the network services it offers according to its state.
+ 4. IBD nodes can fully sync from AssumeUTXO nodes after they finish the background-chain sync.
+ """
+ self.log.info("Testing IBD-sync from assumeUTXO node")
+ # Node2 starts clean and loads the snapshot.
+ # Node3 starts clean and seeks to sync-up from snapshot_node.
+ miner = self.nodes[0]
+ snapshot_node = self.nodes[2]
+ ibd_node = self.nodes[3]
+
+ # Start test fresh by cleaning up node directories
+ for node in (snapshot_node, ibd_node):
+ self.stop_node(node.index)
+ rmtree(node.chain_path)
+ self.start_node(node.index, extra_args=self.extra_args[node.index])
+
+ # Sync-up headers chain on snapshot_node to load snapshot
+ headers_provider_conn = snapshot_node.add_p2p_connection(P2PInterface())
+ headers_provider_conn.wait_for_getheaders()
+ msg = msg_headers()
+ for block_num in range(1, miner.getblockcount()+1):
+ msg.headers.append(from_hex(CBlockHeader(), miner.getblockheader(miner.getblockhash(block_num), verbose=False)))
+ headers_provider_conn.send_message(msg)
+
+ # Ensure headers arrived
+ default_value = {'status': ''} # No status
+ headers_tip_hash = miner.getbestblockhash()
+ self.wait_until(lambda: next(filter(lambda x: x['hash'] == headers_tip_hash, snapshot_node.getchaintips()), default_value)['status'] == "headers-only")
+ snapshot_node.disconnect_p2ps()
+
+ # Load snapshot
+ snapshot_node.loadtxoutset(snapshot['path'])
+
+ # Connect nodes and verify the ibd_node can sync-up the headers-chain from the snapshot_node
+ self.connect_nodes(ibd_node.index, snapshot_node.index)
+ snapshot_block_hash = snapshot['base_hash']
+ self.wait_until(lambda: next(filter(lambda x: x['hash'] == snapshot_block_hash, ibd_node.getchaintips()), default_value)['status'] == "headers-only")
+
+ # Once the headers-chain is synced, the ibd_node must avoid requesting historical blocks from the snapshot_node.
+ # If it does request such blocks, the snapshot_node will ignore requests it cannot fulfill, causing the ibd_node
+ # to stall. This stall could last for up to 10 min, ultimately resulting in an abrupt disconnection due to the
+ # ibd_node's perceived unresponsiveness.
+ time.sleep(3) # Sleep here because we can't detect when a node avoids requesting blocks from other peer.
+ assert_equal(len(ibd_node.getpeerinfo()[0]['inflight']), 0)
+
+ # Now disconnect nodes and finish background chain sync
+ self.disconnect_nodes(ibd_node.index, snapshot_node.index)
+ self.connect_nodes(snapshot_node.index, miner.index)
+ self.sync_blocks(nodes=(miner, snapshot_node))
+ # Check the base snapshot block was stored and ensure node signals full-node service support
+ self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", snapshot_node.getblock, snapshot_block_hash))
+ self.wait_until(lambda: 'NETWORK' in snapshot_node.getnetworkinfo()['localservicesnames'])
+
+ # Now that the snapshot_node is synced, verify the ibd_node can sync from it
+ self.connect_nodes(snapshot_node.index, ibd_node.index)
+ assert 'NETWORK' in ibd_node.getpeerinfo()[0]['servicesnames']
+ self.sync_blocks(nodes=(ibd_node, snapshot_node))
+
+ def assert_only_network_limited_service(self, node):
+ node_services = node.getnetworkinfo()['localservicesnames']
+ assert 'NETWORK' not in node_services
+ assert 'NETWORK_LIMITED' in node_services
+
def run_test(self):
"""
Bring up two (disconnected) nodes, mine some new blocks on the first,
@@ -295,7 +374,7 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n1.getblockcount(), START_HEIGHT)
self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
- dump_output = n0.dumptxoutset('utxos.dat')
+ dump_output = n0.dumptxoutset('utxos.dat', "latest")
self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot")
assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)
@@ -320,12 +399,16 @@ class AssumeutxoTest(BitcoinTestFramework):
for n in self.nodes:
assert_equal(n.getblockchaininfo()["headers"], SNAPSHOT_BASE_HEIGHT)
- assert_equal(
- dump_output['txoutset_hash'],
- "a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
- assert_equal(dump_output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
+ def check_dump_output(output):
+ assert_equal(
+ output['txoutset_hash'],
+ "a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
+ assert_equal(output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
+
+ check_dump_output(dump_output)
+
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
# will allow us to test n1's sync-to-tip on top of a snapshot.
self.generate(n0, nblocks=100, sync_fun=self.no_op)
@@ -335,6 +418,39 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
+ self.log.info(f"Check that dumptxoutset works for past block heights")
+ # rollback defaults to the snapshot base height
+ dump_output2 = n0.dumptxoutset('utxos2.dat', "rollback")
+ check_dump_output(dump_output2)
+ assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output2['path']))
+
+ # Rollback with specific height
+ dump_output3 = n0.dumptxoutset('utxos3.dat', rollback=SNAPSHOT_BASE_HEIGHT)
+ check_dump_output(dump_output3)
+ assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output3['path']))
+
+ # Specified height that is not a snapshot height
+ prev_snap_height = SNAPSHOT_BASE_HEIGHT - 1
+ dump_output4 = n0.dumptxoutset(path='utxos4.dat', rollback=prev_snap_height)
+ assert_equal(
+ dump_output4['txoutset_hash'],
+ "8a1db0d6e958ce0d7c963bc6fc91ead596c027129bacec68acc40351037b09d7")
+ assert sha256sum_file(dump_output['path']) != sha256sum_file(dump_output4['path'])
+
+ # 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)
+ assert_equal(sha256sum_file(dump_output4['path']), sha256sum_file(dump_output5['path']))
+
+ # TODO: This is a hack to set m_best_header to the correct value after
+ # dumptxoutset/reconsiderblock. Otherwise the wrong error messages are
+ # returned in following tests. It can be removed once this bug is
+ # fixed. See also https://github.com/bitcoin/bitcoin/issues/26245
+ self.restart_node(0, ["-reindex"])
+
+ # Ensure n0 is back at the tip
+ assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
+
self.test_snapshot_with_less_work(dump_output['path'])
self.test_invalid_mempool_state(dump_output['path'])
self.test_invalid_snapshot_scenarios(dump_output['path'])
@@ -343,6 +459,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.test_snapshot_block_invalidated(dump_output['path'])
self.test_snapshot_not_on_most_work_chain(dump_output['path'])
+ # Prune-node sanity check
+ assert 'NETWORK' not in n1.getnetworkinfo()['localservicesnames']
+
self.log.info(f"Loading snapshot into second node from {dump_output['path']}")
# This node's tip is on an ancestor block of the snapshot, which should
# be the normal case
@@ -350,6 +469,10 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
+ self.log.info("Confirm that local services remain unchanged")
+ # Since n1 is a pruned node, the 'NETWORK' service flag must always be unset.
+ self.assert_only_network_limited_service(n1)
+
self.log.info("Check that UTXO-querying RPCs operate on snapshot chainstate")
snapshot_hash = loaded['tip_hash']
snapshot_num_coins = loaded['coins_loaded']
@@ -362,7 +485,7 @@ class AssumeutxoTest(BitcoinTestFramework):
# find coinbase output at snapshot height on node0 and scan for it on node1,
# where the block is not available, but the snapshot was loaded successfully
coinbase_tx = n0.getblock(snapshot_hash, verbosity=2)['tx'][0]
- assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, snapshot_hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, snapshot_hash)
coinbase_output_descriptor = coinbase_tx['vout'][0]['scriptPubKey']['desc']
scan_result = n1.scantxoutset('start', [coinbase_output_descriptor])
assert_equal(scan_result['success'], True)
@@ -434,7 +557,7 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Submit a spending transaction for a snapshot chainstate coin to the mempool")
# spend the coinbase output of the first block that is not available on node1
spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1)
- assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, spend_coin_blockhash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, spend_coin_blockhash)
prev_tx = n0.getblock(spend_coin_blockhash, 3)['tx'][0]
prevout = {"txid": prev_tx['txid'], "vout": 0, "scriptPubKey": prev_tx['vout'][0]['scriptPubKey']['hex']}
privkey = n0.get_deterministic_priv_key().key
@@ -453,6 +576,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.restart_node(1, extra_args=[
f"-stopatheight={PAUSE_HEIGHT}", *self.extra_args[1]])
+ # Upon restart during snapshot tip sync, the node must remain in 'limited' mode.
+ self.assert_only_network_limited_service(n1)
+
# Finally connect the nodes and let them sync.
#
# Set `wait_for_connect=False` to avoid a race between performing connection
@@ -469,6 +595,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Restarted node before snapshot validation completed, reloading...")
self.restart_node(1, extra_args=self.extra_args[1])
+ # Upon restart, the node must remain in 'limited' mode
+ self.assert_only_network_limited_service(n1)
+
# Send snapshot block to n1 out of order. This makes the test less
# realistic because normally the snapshot block is one of the last
# blocks downloaded, but its useful to test because it triggers more
@@ -487,6 +616,10 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1)
+ # Since n1 is a pruned node, it will not signal NODE_NETWORK after
+ # completing the background sync.
+ self.assert_only_network_limited_service(n1)
+
# Ensure indexes have synced.
completed_idx_state = {
'basic block filter index': COMPLETE_IDX,
@@ -517,12 +650,18 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("-- Testing all indexes + reindex")
assert_equal(n2.getblockcount(), START_HEIGHT)
+ assert 'NETWORK' in n2.getnetworkinfo()['localservicesnames'] # sanity check
self.log.info(f"Loading snapshot into third node from {dump_output['path']}")
loaded = n2.loadtxoutset(dump_output['path'])
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
+ # Even though n2 is a full node, it will unset the 'NETWORK' service flag during snapshot loading.
+ # This indicates other peers that the node will temporarily not provide historical blocks.
+ self.log.info("Check node2 updated the local services during snapshot load")
+ self.assert_only_network_limited_service(n2)
+
for reindex_arg in ['-reindex=1', '-reindex-chainstate=1']:
self.log.info(f"Check that restarting with {reindex_arg} will delete the snapshot chainstate")
self.restart_node(2, extra_args=[reindex_arg, *self.extra_args[2]])
@@ -546,6 +685,11 @@ class AssumeutxoTest(BitcoinTestFramework):
msg = "Unable to load UTXO snapshot: Can't activate a snapshot-based chainstate more than once"
assert_raises_rpc_error(-32603, msg, n2.loadtxoutset, dump_output['path'])
+ # Upon restart, the node must stay in 'limited' mode until the background
+ # chain sync completes.
+ self.restart_node(2, extra_args=self.extra_args[2])
+ self.assert_only_network_limited_service(n2)
+
self.connect_nodes(0, 2)
self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)
self.sync_blocks(nodes=(n0, n2))
@@ -553,6 +697,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n2.getchainstates()['chainstates']) == 1)
+ # Once background chain sync completes, the full node must start offering historical blocks again.
+ self.wait_until(lambda: {'NETWORK', 'NETWORK_LIMITED'}.issubset(n2.getnetworkinfo()['localservicesnames']))
+
completed_idx_state = {
'basic block filter index': COMPLETE_IDX,
'coinstatsindex': COMPLETE_IDX,
@@ -587,6 +734,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.test_snapshot_in_a_divergent_chain(dump_output['path'])
+ # The following test cleans node2 and node3 chain directories.
+ self.test_sync_from_assumeutxo_node(snapshot=dump_output)
+
@dataclass
class Block:
hash: str
diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py
index 03a9f666b2..32ad3ad271 100755
--- a/test/functional/feature_assumevalid.py
+++ b/test/functional/feature_assumevalid.py
@@ -139,8 +139,8 @@ class AssumeValidTest(BitcoinTestFramework):
height += 1
# Start node1 and node2 with assumevalid so they accept a block with a bad signature.
- self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)])
- self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)])
+ self.start_node(1, extra_args=["-assumevalid=" + block102.hash])
+ self.start_node(2, extra_args=["-assumevalid=" + block102.hash])
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
p2p0.send_header_for_blocks(self.blocks[0:2000])
diff --git a/test/functional/feature_blocksxor.py b/test/functional/feature_blocksxor.py
index 88e0244cd4..7698a66ec4 100755
--- a/test/functional/feature_blocksxor.py
+++ b/test/functional/feature_blocksxor.py
@@ -3,14 +3,15 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test support for XORed block data and undo files (`-blocksxor` option)."""
-import os
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.test_node import ErrorMatch
+from test_framework.test_node import (
+ ErrorMatch,
+ NULL_BLK_XOR_KEY,
+)
from test_framework.util import (
assert_equal,
assert_greater_than,
- read_xor_key,
util_xor,
)
from test_framework.wallet import MiniWallet
@@ -40,7 +41,7 @@ class BlocksXORTest(BitcoinTestFramework):
self.log.info("Shut down node and un-XOR block/undo files manually")
self.stop_node(0)
- xor_key = read_xor_key(node=node)
+ xor_key = node.read_xor_key()
for data_file in sorted(block_files + undo_files):
self.log.debug(f"Rewriting file {data_file}...")
with open(data_file, 'rb+') as f:
@@ -54,11 +55,13 @@ class BlocksXORTest(BitcoinTestFramework):
match=ErrorMatch.PARTIAL_REGEX)
self.log.info("Delete XOR key, restart node with '-blocksxor=0', check blk*.dat/rev*.dat file integrity")
- os.remove(node.blocks_path / 'xor.dat')
+ node.blocks_key_path.unlink()
self.start_node(0, extra_args=['-blocksxor=0'])
# checklevel=2 -> verify block validity + undo data
# nblocks=0 -> verify all blocks
node.verifychain(checklevel=2, nblocks=0)
+ self.log.info("Check that blocks XOR key is recreated")
+ assert_equal(node.read_xor_key(), NULL_BLK_XOR_KEY)
if __name__ == '__main__':
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index bb20e2baa8..44c7edf962 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -153,6 +153,13 @@ class ConfArgsTest(BitcoinTestFramework):
expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.',
extra_args=['-proxy'],
)
+ # Provide a value different from 1 to the -wallet negated option
+ if self.is_wallet_compiled():
+ for value in [0, 'not_a_boolean']:
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets",
+ extra_args=[f'-nowallet={value}'],
+ )
def test_log_buffer(self):
self.stop_node(0)
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index a3dcb7afda..83627ff5c2 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code."""
from copy import deepcopy
-from decimal import Decimal
+from decimal import Decimal, ROUND_DOWN
import os
import random
import time
@@ -40,7 +40,7 @@ def small_txpuzzle_randfee(
# Exponentially distributed from 1-128 * fee_increment
rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))
# Total fee ranges from min_fee to min_fee + 127*fee_increment
- fee = min_fee - fee_increment + satoshi_round(rand_fee)
+ fee = min_fee - fee_increment + satoshi_round(rand_fee, rounding=ROUND_DOWN)
utxos_to_spend = []
total_in = Decimal("0.00000000")
while total_in <= (amount + fee) and len(conflist) > 0:
diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py
index 8327a0477b..34228f6f38 100755
--- a/test/functional/feature_minchainwork.py
+++ b/test/functional/feature_minchainwork.py
@@ -110,7 +110,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
self.stop_node(0)
self.nodes[0].assert_start_raises_init_error(
["-minimumchainwork=test"],
- expected_msg='Error: Invalid non-hex (test) minimum chain work value specified',
+ expected_msg='Error: Invalid minimum work specified (test), must be up to 64 hex digits',
)
diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py
index 2961a2d356..1ebfe82da5 100755
--- a/test/functional/feature_reindex.py
+++ b/test/functional/feature_reindex.py
@@ -14,7 +14,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import MAGIC_BYTES
from test_framework.util import (
assert_equal,
- read_xor_key,
util_xor,
)
@@ -43,7 +42,7 @@ class ReindexTest(BitcoinTestFramework):
# we're generating them rather than getting them from peers), so to
# test out-of-order handling, swap blocks 1 and 2 on disk.
blk0 = self.nodes[0].blocks_path / "blk00000.dat"
- xor_dat = read_xor_key(node=self.nodes[0])
+ xor_dat = self.nodes[0].read_xor_key()
with open(blk0, 'r+b') as bf:
# Read at least the first few blocks (including genesis)
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
index 2189eac7dd..a7294944bf 100755
--- a/test/functional/feature_settings.py
+++ b/test/functional/feature_settings.py
@@ -13,11 +13,32 @@ from test_framework.util import assert_equal
class SettingsTest(BitcoinTestFramework):
+ def add_options(self, parser):
+ self.add_wallet_options(parser)
+
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.wallet_names = []
+ def test_wallet_settings(self, settings_path):
+ if not self.is_wallet_compiled():
+ return
+
+ self.log.info("Testing wallet settings..")
+ node = self.nodes[0]
+ # Create wallet to use it during tests
+ self.start_node(0)
+ node.createwallet(wallet_name='w1')
+ self.stop_node(0)
+
+ # Verify wallet settings can only be strings. Either names or paths. Not booleans, nums nor anything else.
+ for wallets_data in [[10], [True], [[]], [{}], ["w1", 10], ["w1", False]]:
+ with settings_path.open("w") as fp:
+ json.dump({"wallet": wallets_data}, fp)
+ node.assert_start_raises_init_error(expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets",
+ extra_args=[f'-settings={settings_path}'])
+
def run_test(self):
node, = self.nodes
settings = node.chain_path / "settings.json"
@@ -86,6 +107,8 @@ class SettingsTest(BitcoinTestFramework):
self.start_node(0, extra_args=[f"-settings={altsettings}"])
self.stop_node(0)
+ self.test_wallet_settings(settings)
+
if __name__ == '__main__':
SettingsTest(__file__).main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index e7113f8335..30bf97185a 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -381,6 +381,9 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcwait', '-rpcwaittimeout=5').echo)
assert_greater_than_or_equal(time.time(), start_time + 5)
+ self.log.info("Test that only one of -addrinfo, -generate, -getinfo, -netinfo may be specified at a time")
+ assert_raises_process_error(1, "Only one of -getinfo, -netinfo may be specified", self.nodes[0].cli('-getinfo', '-netinfo').send_cli)
+
if __name__ == '__main__':
TestBitcoinCli(__file__).main()
diff --git a/test/functional/interface_usdt_coinselection.py b/test/functional/interface_usdt_coinselection.py
index dc40986a75..f684848aed 100755
--- a/test/functional/interface_usdt_coinselection.py
+++ b/test/functional/interface_usdt_coinselection.py
@@ -181,7 +181,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 5. aps_create_tx_internal (type 4)
wallet.sendtoaddress(wallet.getnewaddress(), 10)
events = self.get_tracepoints([1, 2, 3, 1, 4])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_greater_than(change_pos, -1)
@@ -190,7 +190,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 1. normal_create_tx_internal (type 2)
assert_raises_rpc_error(-6, "Insufficient funds", wallet.sendtoaddress, wallet.getnewaddress(), 102 * 50)
events = self.get_tracepoints([2])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, False)
self.log.info("Explicitly enabling APS results in 2 tracepoints")
@@ -200,7 +200,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
wallet.setwalletflag("avoid_reuse")
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=10, avoid_reuse=True)
events = self.get_tracepoints([1, 2])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_equal(use_aps, None)
@@ -213,7 +213,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 5. aps_create_tx_internal (type 4)
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True, avoid_reuse=False)
events = self.get_tracepoints([1, 2, 3, 1, 4])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_equal(change_pos, -1)
@@ -223,7 +223,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 2. normal_create_tx_internal (type 2)
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True)
events = self.get_tracepoints([1, 2])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_equal(change_pos, -1)
diff --git a/test/functional/mempool_package_rbf.py b/test/functional/mempool_package_rbf.py
index 9b4269f0a0..f4d57262f2 100755
--- a/test/functional/mempool_package_rbf.py
+++ b/test/functional/mempool_package_rbf.py
@@ -189,7 +189,7 @@ class PackageRBFTest(BitcoinTestFramework):
package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
node.submitpackage(package_hex4)
self.assert_mempool_contents(expected=package_txns4)
- package_hex5, package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
+ package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
pkg_results5 = node.submitpackage(package_hex5)
assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
self.assert_mempool_contents(expected=package_txns4)
@@ -336,16 +336,16 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=expected_txns)
# Now make conflicting packages for each coin
- package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex1)
assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
- package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex2)
assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has both ancestor and descendant, exceeding cluster limit of 2", package_result["package_msg"])
- package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex3)
assert_equal(f"package RBF failed: {grandchild_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
@@ -389,15 +389,15 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=expected_txns)
# Now make conflicting packages for each coin
- package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex1)
assert_equal(f"package RBF failed: {parent1_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"])
- package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex2)
assert_equal(f"package RBF failed: {parent2_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"])
- package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex3)
assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
@@ -443,15 +443,15 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=expected_txns)
# Now make conflicting packages for each coin
- package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex1)
assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
- package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex2)
assert_equal(f"package RBF failed: {child1_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
- package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex3)
assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
@@ -519,7 +519,7 @@ class PackageRBFTest(BitcoinTestFramework):
# Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though
# total fees are higher than the original package
- package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
pkg_results2 = node.submitpackage(package_hex2)
assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram')
self.assert_mempool_contents(expected=package_txns1)
diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py
index 4656176a75..47df0c614a 100755
--- a/test/functional/mempool_sigoplimit.py
+++ b/test/functional/mempool_sigoplimit.py
@@ -154,7 +154,7 @@ class BytesPerSigOpTest(BitcoinTestFramework):
return (tx_utxo, tx)
tx_parent_utxo, tx_parent = create_bare_multisig_tx()
- tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo)
+ _tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo)
# Separately, the parent tx is ok
parent_individual_testres = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex()])[0]
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index c0df120c65..aca71933ec 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -159,6 +159,15 @@ class MiningTest(BitcoinTestFramework):
bad_block.solve()
assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()))
+ self.log.info("Test timewarp protection boundary")
+ bad_block.nTime = t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP - 1
+ bad_block.solve()
+ assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()))
+
+ bad_block.nTime = t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP
+ bad_block.solve()
+ node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex())
+
def run_test(self):
node = self.nodes[0]
self.wallet = MiniWallet(node)
diff --git a/test/functional/p2p_1p1c_network.py b/test/functional/p2p_1p1c_network.py
index c3cdb3e0b3..f9e782f524 100755
--- a/test/functional/p2p_1p1c_network.py
+++ b/test/functional/p2p_1p1c_network.py
@@ -107,7 +107,7 @@ class PackageRelayTest(BitcoinTestFramework):
# 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
# We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
- package_hex_3, parent_31, parent_32, child_3 = self.create_package_2p1c(self.wallet)
+ package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
# 4: parent + child package where the child spends 2 different outputs from the parent.
package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py
index 9055232cf3..6e7b4b399e 100755
--- a/test/functional/p2p_headers_sync_with_minchainwork.py
+++ b/test/functional/p2p_headers_sync_with_minchainwork.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2019-2022 The Bitcoin Core developers
+# Copyright (c) 2019-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat"""
@@ -21,6 +21,8 @@ from test_framework.blocktools import (
from test_framework.util import assert_equal
+import time
+
NODE1_BLOCKS_REQUIRED = 15
NODE2_BLOCKS_REQUIRED = 2047
@@ -48,6 +50,10 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.connect_nodes(0, 2)
self.connect_nodes(0, 3)
+ def mocktime_all(self, time):
+ for n in self.nodes:
+ n.setmocktime(time)
+
def test_chains_sync_when_long_enough(self):
self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree")
with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]):
@@ -149,7 +155,9 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.reconnect_all()
+ self.mocktime_all(int(time.time())) # Temporarily hold time to avoid internal timeouts
self.sync_blocks(timeout=300) # Ensure tips eventually agree
+ self.mocktime_all(0)
def run_test(self):
diff --git a/test/functional/p2p_ibd_stalling.py b/test/functional/p2p_ibd_stalling.py
index 11cd8837f4..fa07873929 100755
--- a/test/functional/p2p_ibd_stalling.py
+++ b/test/functional/p2p_ibd_stalling.py
@@ -73,6 +73,7 @@ class P2PIBDStallingTest(BitcoinTestFramework):
peers = []
self.log.info("Check that a staller does not get disconnected if the 1024 block lookahead buffer is filled")
+ self.mocktime = int(time.time()) + 1
for id in range(NUM_PEERS):
peers.append(node.add_outbound_p2p_connection(P2PStaller(stall_block), p2p_idx=id, connection_type="outbound-full-relay"))
peers[-1].block_store = block_dict
@@ -85,7 +86,7 @@ class P2PIBDStallingTest(BitcoinTestFramework):
self.all_sync_send_with_ping(peers)
# If there was a peer marked for stalling, it would get disconnected
- self.mocktime = int(time.time()) + 3
+ self.mocktime += 3
node.setmocktime(self.mocktime)
self.all_sync_send_with_ping(peers)
assert_equal(node.num_test_p2p_connections(), NUM_PEERS)
diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py
index df6e6a2e28..7788be6adb 100755
--- a/test/functional/p2p_node_network_limited.py
+++ b/test/functional/p2p_node_network_limited.py
@@ -102,10 +102,10 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
tip_height = pruned_node.getblockcount()
limit_buffer = 2
# Prevent races by waiting for the tip to arrive first
- self.wait_until(lambda: not try_rpc(-1, "Block not found", full_node.getblock, pruned_node.getbestblockhash()))
+ self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getbestblockhash()))
for height in range(start_height_full_node + 1, tip_height + 1):
if height <= tip_height - (NODE_NETWORK_LIMITED_MIN_BLOCKS - limit_buffer):
- assert_raises_rpc_error(-1, "Block not found on disk", full_node.getblock, pruned_node.getblockhash(height))
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getblockhash(height))
else:
full_node.getblock(pruned_node.getblockhash(height)) # just assert it does not throw an exception
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index c881dd6ff4..c37061c307 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -14,8 +14,10 @@ from test_framework.p2p import P2PDataStore
from test_framework.test_node import ErrorMatch
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
+ append_config,
assert_equal,
p2p_port,
+ tor_port,
)
from test_framework.wallet import MiniWallet
@@ -57,11 +59,14 @@ class P2PPermissionsTests(BitcoinTestFramework):
# by modifying the configuration file.
ip_port = "127.0.0.1:{}".format(p2p_port(1))
self.nodes[1].replace_in_config([("bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port)])
+ # Explicitly bind the tor port to prevent collisions with the default tor port
+ append_config(self.nodes[1].datadir_path, [f"bind=127.0.0.1:{tor_port(self.nodes[1].index)}=onion"])
self.checkpermission(
["-whitelist=noban@127.0.0.1"],
# Check parameter interaction forcerelay should activate relay
["noban", "bloomfilter", "forcerelay", "relay", "download"])
self.nodes[1].replace_in_config([("whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1")])
+ self.nodes[1].replace_in_config([(f"bind=127.0.0.1:{tor_port(self.nodes[1].index)}=onion", "")])
self.checkpermission(
# legacy whitelistrelay should be ignored
diff --git a/test/functional/p2p_seednode.py b/test/functional/p2p_seednode.py
new file mode 100755
index 0000000000..6c510a6a0b
--- /dev/null
+++ b/test/functional/p2p_seednode.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019-2021 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+"""
+Test seednode interaction with the AddrMan
+"""
+import random
+import time
+
+from test_framework.test_framework import BitcoinTestFramework
+
+ADD_NEXT_SEEDNODE = 10
+
+
+class P2PSeedNodes(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.disable_autoconnect = False
+
+ def test_no_seednode(self):
+ # Check that if no seednode is provided, the node proceeds as usual (without waiting)
+ with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=["Empty addrman, adding seednode", f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode"], timeout=ADD_NEXT_SEEDNODE):
+ self.restart_node(0)
+
+ def test_seednode_empty_addrman(self):
+ seed_node = "0.0.0.1"
+ # Check that the seednode is added to m_addr_fetches on bootstrap on an empty addrman
+ with self.nodes[0].assert_debug_log(expected_msgs=[f"Empty addrman, adding seednode ({seed_node}) to addrfetch"], timeout=ADD_NEXT_SEEDNODE):
+ self.restart_node(0, extra_args=[f'-seednode={seed_node}'])
+
+ def test_seednode_addrman_unreachable_peers(self):
+ seed_node = "0.0.0.2"
+ node = self.nodes[0]
+ # Fill the addrman with unreachable nodes
+ for i in range(10):
+ ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}"
+ port = 8333 + i
+ node.addpeeraddress(ip, port)
+
+ # Restart the node so seednode is processed again
+ with node.assert_debug_log(expected_msgs=[f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode ({seed_node}) to addrfetch"], unexpected_msgs=["Empty addrman, adding seednode"], timeout=ADD_NEXT_SEEDNODE * 1.5):
+ self.restart_node(0, extra_args=[f'-seednode={seed_node}'])
+ node.setmocktime(int(time.time()) + ADD_NEXT_SEEDNODE + 1)
+
+ def run_test(self):
+ self.test_no_seednode()
+ self.test_seednode_empty_addrman()
+ self.test_seednode_addrman_unreachable_peers()
+
+
+if __name__ == '__main__':
+ P2PSeedNodes(__file__).main()
+
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 11b4d9cc3b..efad4e7c0f 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -156,9 +156,9 @@ class TxDownloadTest(BitcoinTestFramework):
# One of the peers is asked for the tx
peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)
with p2p_lock:
- peer_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)
+ _peer_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)
assert_equal(peer_fallback.tx_getdata_count, 0)
- self.nodes[0].setmocktime(int(time.time()) + GETDATA_TX_INTERVAL + 1) # Wait for request to peer_expiry to expire
+ self.nodes[0].setmocktime(int(time.time()) + GETDATA_TX_INTERVAL + 1) # Wait for request to _peer_expiry to expire
peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)
self.restart_node(0) # reset mocktime
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index 835ecbf184..1430131a97 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -119,7 +119,7 @@ class AcceptBlockTest(BitcoinTestFramework):
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert tip_entry_found
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_h1f.hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_h1f.hash)
# 4. Send another two block that build on the fork.
block_h2f = create_block(block_h1f.sha256, create_coinbase(2), block_time)
@@ -191,7 +191,7 @@ class AcceptBlockTest(BitcoinTestFramework):
# Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead
for x in all_blocks[:-1]:
self.nodes[0].getblock(x.hash)
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[-1].hash)
# 5. Test handling of unrequested block on the node that didn't process
# Should still not be processed (even though it has a child that has more
@@ -230,7 +230,7 @@ class AcceptBlockTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getblockcount(), 290)
self.nodes[0].getblock(all_blocks[286].hash)
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[287].hash)
self.log.info("Successfully reorged to longer chain")
# 8. Create a chain which is invalid at a height longer than the
@@ -260,7 +260,7 @@ class AcceptBlockTest(BitcoinTestFramework):
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert tip_entry_found
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_292.hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_292.hash)
test_node.send_message(msg_block(block_289f))
test_node.send_and_ping(msg_block(block_290f))
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 98147237b1..91e9975ad5 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -32,14 +32,16 @@ from test_framework.blocktools import (
TIME_GENESIS_BLOCK,
create_block,
create_coinbase,
+ create_tx_with_script,
)
from test_framework.messages import (
CBlockHeader,
+ COIN,
from_hex,
msg_block,
)
from test_framework.p2p import P2PInterface
-from test_framework.script import hash256
+from test_framework.script import hash256, OP_TRUE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -556,12 +558,12 @@ class BlockchainTest(BitcoinTestFramework):
block = node.getblock(blockhash, verbosity)
assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex())
- def assert_fee_not_in_block(verbosity):
- block = node.getblock(blockhash, verbosity)
+ def assert_fee_not_in_block(hash, verbosity):
+ block = node.getblock(hash, verbosity)
assert 'fee' not in block['tx'][1]
- def assert_fee_in_block(verbosity):
- block = node.getblock(blockhash, verbosity)
+ def assert_fee_in_block(hash, verbosity):
+ block = node.getblock(hash, verbosity)
tx = block['tx'][1]
assert 'fee' in tx
assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
@@ -580,8 +582,8 @@ class BlockchainTest(BitcoinTestFramework):
total_vout += vout["value"]
assert_equal(total_vin, total_vout + tx["fee"])
- def assert_vin_does_not_contain_prevout(verbosity):
- block = node.getblock(blockhash, verbosity)
+ def assert_vin_does_not_contain_prevout(hash, verbosity):
+ block = node.getblock(hash, verbosity)
tx = block["tx"][1]
if isinstance(tx, str):
# In verbosity level 1, only the transaction hashes are written
@@ -595,16 +597,16 @@ class BlockchainTest(BitcoinTestFramework):
assert_hexblock_hashes(False)
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
- assert_fee_not_in_block(1)
- assert_fee_not_in_block(True)
+ assert_fee_not_in_block(blockhash, 1)
+ assert_fee_not_in_block(blockhash, True)
self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
- assert_fee_in_block(2)
- assert_fee_in_block(3)
+ assert_fee_in_block(blockhash, 2)
+ assert_fee_in_block(blockhash, 3)
self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout")
- assert_vin_does_not_contain_prevout(1)
- assert_vin_does_not_contain_prevout(2)
+ assert_vin_does_not_contain_prevout(blockhash, 1)
+ assert_vin_does_not_contain_prevout(blockhash, 2)
self.log.info("Test that getblock with verbosity 3 includes prevout")
assert_vin_contains_prevout(3)
@@ -612,7 +614,7 @@ class BlockchainTest(BitcoinTestFramework):
self.log.info("Test getblock with invalid verbosity type returns proper error message")
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2")
- self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data")
+ self.log.info("Test that getblock doesn't work with deleted Undo data")
def move_block_file(old, new):
old_path = self.nodes[0].blocks_path / old
@@ -622,10 +624,8 @@ class BlockchainTest(BitcoinTestFramework):
# Move instead of deleting so we can restore chain state afterwards
move_block_file('rev00000.dat', 'rev_wrong')
- assert_fee_not_in_block(2)
- assert_fee_not_in_block(3)
- assert_vin_does_not_contain_prevout(2)
- assert_vin_does_not_contain_prevout(3)
+ assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 2))
+ assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 3))
# Restore chain state
move_block_file('rev_wrong', 'rev00000.dat')
@@ -633,6 +633,31 @@ class BlockchainTest(BitcoinTestFramework):
assert 'previousblockhash' not in node.getblock(node.getblockhash(0))
assert 'nextblockhash' not in node.getblock(node.getbestblockhash())
+ self.log.info("Test getblock when only header is known")
+ current_height = node.getblock(node.getbestblockhash())['height']
+ block_time = node.getblock(node.getbestblockhash())['time'] + 1
+ block = create_block(int(blockhash, 16), create_coinbase(current_height + 1, nValue=100), block_time)
+ block.solve()
+ node.submitheader(block.serialize().hex())
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: node.getblock(block.hash))
+
+ self.log.info("Test getblock when block data is available but undo data isn't")
+ # Submits a block building on the header-only block, so it can't be connected and has no undo data
+ tx = create_tx_with_script(block.vtx[0], 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN)
+ block_noundo = create_block(block.sha256, create_coinbase(current_height + 2, nValue=100), block_time + 1, txlist=[tx])
+ block_noundo.solve()
+ node.submitblock(block_noundo.serialize().hex())
+
+ assert_fee_not_in_block(block_noundo.hash, 2)
+ assert_fee_not_in_block(block_noundo.hash, 3)
+ assert_vin_does_not_contain_prevout(block_noundo.hash, 2)
+ assert_vin_does_not_contain_prevout(block_noundo.hash, 3)
+
+ self.log.info("Test getblock when block is missing")
+ move_block_file('blk00000.dat', 'blk00000.dat.bak')
+ assert_raises_rpc_error(-1, "Block not found on disk", node.getblock, blockhash)
+ move_block_file('blk00000.dat.bak', 'blk00000.dat')
+
if __name__ == '__main__':
BlockchainTest(__file__).main()
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 9f4e17a328..d95820bbf8 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -47,7 +47,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
return node.get_wallet_rpc(wallet_name)
def run_test(self):
- node0, node1, node2 = self.nodes
+ node0, node1, _node2 = self.nodes
self.wallet = MiniWallet(test_node=node0)
if self.is_wallet_compiled():
@@ -122,7 +122,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts", wallet_multi.addmultisigaddress, 16, pubkeys, '', 'bech32')
def do_multisig(self, nkeys, nsigs, output_type, wallet_multi):
- node0, node1, node2 = self.nodes
+ node0, _node1, node2 = self.nodes
pub_keys = self.pub[0: nkeys]
priv_keys = self.priv[0: nkeys]
diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py
index c66d91713f..32fdfa350a 100755
--- a/test/functional/rpc_deriveaddresses.py
+++ b/test/functional/rpc_deriveaddresses.py
@@ -29,6 +29,9 @@ class DeriveaddressesTest(BitcoinTestFramework):
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
+ ranged_descriptor = descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/<0;1>/*)")
+ assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), [["bcrt1q7c8mdmdktrzs8xgpjmqw90tjn65j5a3yj04m3n", "bcrt1qs6n37uzu0v0qfzf0r0csm0dwa7prc0v5uavgy0"], ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]])
+
assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), [0, 2])
assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"))
diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py
index aa12da6ceb..ad05060210 100755
--- a/test/functional/rpc_dumptxoutset.py
+++ b/test/functional/rpc_dumptxoutset.py
@@ -19,6 +19,17 @@ class DumptxoutsetTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
+ def check_expected_network(self, node, active):
+ rev_file = node.blocks_path / "rev00000.dat"
+ bogus_file = node.blocks_path / "bogus.dat"
+ rev_file.rename(bogus_file)
+ assert_raises_rpc_error(
+ -1, 'Could not roll back to requested height.', node.dumptxoutset, 'utxos.dat', rollback=99)
+ assert_equal(node.getnetworkinfo()['networkactive'], active)
+
+ # Cleanup
+ bogus_file.rename(rev_file)
+
def run_test(self):
"""Test a trivial usage of the dumptxoutset RPC command."""
node = self.nodes[0]
@@ -27,8 +38,8 @@ class DumptxoutsetTest(BitcoinTestFramework):
self.generate(node, COINBASE_MATURITY)
FILENAME = 'txoutset.dat'
- out = node.dumptxoutset(FILENAME)
- expected_path = node.datadir_path / self.chain / FILENAME
+ out = node.dumptxoutset(FILENAME, "latest")
+ expected_path = node.chain_path / FILENAME
assert expected_path.is_file()
@@ -51,10 +62,22 @@ class DumptxoutsetTest(BitcoinTestFramework):
# Specifying a path to an existing or invalid file will fail.
assert_raises_rpc_error(
- -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME)
+ -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME, "latest")
invalid_path = node.datadir_path / "invalid" / "path"
assert_raises_rpc_error(
- -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path)
+ -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path, "latest")
+
+ self.log.info(f"Test that dumptxoutset with unknown dump type fails")
+ assert_raises_rpc_error(
+ -8, 'Invalid snapshot type "bogus" specified. Please specify "rollback" or "latest"', node.dumptxoutset, 'utxos.dat', "bogus")
+
+ self.log.info(f"Test that dumptxoutset failure does not leave the network activity suspended when it was on previously")
+ self.check_expected_network(node, True)
+
+ self.log.info(f"Test that dumptxoutset failure leaves the network activity suspended when it was off")
+ node.setnetworkactive(False)
+ self.check_expected_network(node, False)
+ node.setnetworkactive(True)
if __name__ == '__main__':
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index e309018516..62b3d664e0 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -58,7 +58,7 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Node 0 should only have the header for node 1's block 3")
x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips()))
assert_equal(x['status'], "headers-only")
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, short_tip)
self.log.info("Fetch block from node 1")
peers = self.nodes[0].getpeerinfo()
diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py
index d1e4895eb6..002763201a 100755
--- a/test/functional/rpc_getblockstats.py
+++ b/test/functional/rpc_getblockstats.py
@@ -114,7 +114,7 @@ class GetblockstatsTest(BitcoinTestFramework):
assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos)
for i in range(self.max_stat_pos+1):
- self.log.info('Checking block %d\n' % (i))
+ self.log.info('Checking block %d' % (i))
assert_equal(stats[i], self.expected_stats[i])
# Check selecting block by hash too
@@ -182,5 +182,16 @@ class GetblockstatsTest(BitcoinTestFramework):
assert_equal(tip_stats["utxo_increase_actual"], 4)
assert_equal(tip_stats["utxo_size_inc_actual"], 300)
+ 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']))
+
+ self.log.info('Test when block is missing')
+ (self.nodes[0].blocks_path / 'blk00000.dat').rename(self.nodes[0].blocks_path / 'blk00000.dat.backup')
+ assert_raises_rpc_error(-1, 'Block not found on disk', self.nodes[0].getblockstats, hash_or_height=1)
+ (self.nodes[0].blocks_path / 'blk00000.dat.backup').rename(self.nodes[0].blocks_path / 'blk00000.dat')
+
+
if __name__ == '__main__':
GetblockstatsTest(__file__).main()
diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py
index c84928462d..e4e6d6f0f3 100755
--- a/test/functional/rpc_getdescriptorinfo.py
+++ b/test/functional/rpc_getdescriptorinfo.py
@@ -19,10 +19,15 @@ class DescriptorTest(BitcoinTestFramework):
self.extra_args = [["-disablewallet"]]
self.wallet_names = []
- def test_desc(self, desc, isrange, issolvable, hasprivatekeys):
+ def test_desc(self, desc, isrange, issolvable, hasprivatekeys, expanded_descs=None):
info = self.nodes[0].getdescriptorinfo(desc)
assert_equal(info, self.nodes[0].getdescriptorinfo(descsum_create(desc)))
- assert_equal(info['descriptor'], descsum_create(desc))
+ if expanded_descs is not None:
+ assert_equal(info["descriptor"], descsum_create(expanded_descs[0]))
+ assert_equal(info["multipath_expansion"], [descsum_create(x) for x in expanded_descs])
+ else:
+ assert_equal(info['descriptor'], descsum_create(desc))
+ assert "multipath_expansion" not in info
assert_equal(info['isrange'], isrange)
assert_equal(info['issolvable'], issolvable)
assert_equal(info['hasprivatekeys'], hasprivatekeys)
@@ -60,6 +65,11 @@ class DescriptorTest(BitcoinTestFramework):
self.test_desc("pkh([d34db33f/44h/0h/0h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)", isrange=True, issolvable=True, hasprivatekeys=False)
# A set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default).
self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0/*))", isrange=True, issolvable=True, hasprivatekeys=False)
+ # A multipath descriptor
+ self.test_desc("wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<0;1>/*)", isrange=True, issolvable=True, hasprivatekeys=False,
+ expanded_descs=["wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/*)", "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)"])
+ self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<1;2>/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<2;3>/0/*))", isrange=True, issolvable=True, hasprivatekeys=False,
+ expanded_descs=["wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/2/0/*))", "wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/2/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/3/0/*))"])
if __name__ == '__main__':
diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py
index e53e2fa910..a7b560ef57 100755
--- a/test/functional/rpc_scantxoutset.py
+++ b/test/functional/rpc_scantxoutset.py
@@ -110,6 +110,7 @@ class ScantxoutsetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500, 1500]}])['total_amount'], Decimal("16.384"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/<0;1>)"}])["total_amount"], Decimal("12.288"))
# Test the reported descriptors for a few matches
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/*)", "range": 1499}])), ["pkh([0c5f9a1e/0h/0h/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#rthll0rg", "pkh([0c5f9a1e/0h/0h/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#mcjajulr"])
diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py
index be96742aa8..b359a08f39 100755
--- a/test/functional/rpc_signrawtransactionwithkey.py
+++ b/test/functional/rpc_signrawtransactionwithkey.py
@@ -4,8 +4,8 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test transaction signing using the signrawtransactionwithkey RPC."""
-from test_framework.blocktools import (
- COINBASE_MATURITY,
+from test_framework.messages import (
+ COIN,
)
from test_framework.address import (
address_to_scriptpubkey,
@@ -16,7 +16,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- find_vout_for_address,
)
from test_framework.script_util import (
key_to_p2pk_script,
@@ -26,6 +25,7 @@ from test_framework.script_util import (
)
from test_framework.wallet import (
getnewdestination,
+ MiniWallet,
)
from test_framework.wallet_util import (
generate_keypair,
@@ -46,16 +46,12 @@ OUTPUTS = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
class SignRawTransactionWithKeyTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = True
- self.num_nodes = 2
+ self.num_nodes = 1
def send_to_address(self, addr, amount):
- input = {"txid": self.nodes[0].getblock(self.block_hash[self.blk_idx])["tx"][0], "vout": 0}
- output = {addr: amount}
- self.blk_idx += 1
- rawtx = self.nodes[0].createrawtransaction([input], output)
- txid = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransactionwithkey(rawtx, [self.nodes[0].get_deterministic_priv_key().key])["hex"], 0)
- return txid
+ script_pub_key = address_to_scriptpubkey(addr)
+ tx = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=script_pub_key, amount=int(amount * COIN))
+ return tx["txid"], tx["sent_vout"]
def assert_signing_completed_successfully(self, signed_tx):
assert 'errors' not in signed_tx
@@ -80,14 +76,12 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
# Create a new P2SH-P2WSH 1-of-1 multisig address:
embedded_privkey, embedded_pubkey = generate_keypair(wif=True)
- p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey.hex()], "p2sh-segwit")
+ p2sh_p2wsh_address = self.nodes[0].createmultisig(1, [embedded_pubkey.hex()], "p2sh-segwit")
# send transaction to P2SH-P2WSH 1-of-1 multisig address
- self.block_hash = self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- self.blk_idx = 0
self.send_to_address(p2sh_p2wsh_address["address"], 49.999)
self.generate(self.nodes[0], 1)
# Get the UTXO info from scantxoutset
- unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
+ unspent_output = self.nodes[0].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
@@ -103,9 +97,9 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
def keyless_signing_test(self):
self.log.info("Test that keyless 'signing' of pay-to-anchor input succeeds")
- funding_txid = self.send_to_address(p2a(), 49.999)
+ [txid, vout] = self.send_to_address(p2a(), 49.999)
spending_tx = self.nodes[0].createrawtransaction(
- [{"txid": funding_txid, "vout": 0}],
+ [{"txid": txid, "vout": vout}],
[{getnewdestination()[2]: Decimal("49.998")}])
spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [], [])
self.assert_signing_completed_successfully(spending_tx_signed)
@@ -124,9 +118,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
addr = script_to_p2sh(redeem_script)
script_pub_key = address_to_scriptpubkey(addr).hex()
# Fund that address
- txid = self.send_to_address(addr, 10)
- vout = find_vout_for_address(self.nodes[0], txid, addr)
- self.generate(self.nodes[0], 1)
+ [txid, vout] = self.send_to_address(addr, 10)
# Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {getnewdestination()[2]: Decimal("9.999")})
spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}])
@@ -149,6 +141,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
assert_raises_rpc_error(-22, "TX decode failed. Make sure the tx has at least one input.", self.nodes[0].signrawtransactionwithkey, tx + "00", privkeys)
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
self.successful_signing_test()
self.witness_script_test()
self.keyless_signing_test()
diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py
index 387132b680..90572245d6 100755
--- a/test/functional/rpc_txoutproof.py
+++ b/test/functional/rpc_txoutproof.py
@@ -67,6 +67,10 @@ class MerkleBlockTest(BitcoinTestFramework):
assert_equal(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid_spent], blockhash)), [txid_spent])
# We can't get the proof if we specify a non-existent block
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].gettxoutproof, [txid_spent], "0000000000000000000000000000000000000000000000000000000000000000")
+ # We can't get the proof if we only have the header of the specified block
+ 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)", self.nodes[0].gettxoutproof, [txid_spent], block['hash'])
# We can get the proof if the transaction is unspent
assert_equal(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid_unspent])), [txid_unspent])
# We can get the proof if we provide a list of transactions and one of them is unspent. The ordering of the list should not matter.
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index 44187ce790..49eb64abad 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -139,15 +139,32 @@ class HTTPBasicsTest(BitcoinTestFramework):
init_error = 'Error: Unable to start HTTP server. See debug log for details.'
self.log.info('Check -rpcauth are validated')
- # Empty -rpcauth= are ignored
- self.restart_node(0, extra_args=['-rpcauth='])
+ self.log.info('Empty -rpcauth are treated as error')
self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth'])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth='])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=""'])
+ self.log.info('Check malformed -rpcauth')
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar:baz'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar:baz'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz'])
+ self.log.info('Check interactions between blank and non-blank rpcauth')
+ # pw = bitcoin
+ rpcauth_user1 = '-rpcauth=user1:6dd184e5e69271fdd69103464630014f$eb3d7ce67c4d1ff3564270519b03b636c0291012692a5fa3dd1d2075daedd07b'
+ rpcauth_user2 = '-rpcauth=user2:57b2f77c919eece63cfa46c2f06e46ae$266b63902f99f97eeaab882d4a87f8667ab84435c3799f2ce042ef5a994d620b'
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=[rpcauth_user1, rpcauth_user2, '-rpcauth='])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=[rpcauth_user1, '-rpcauth=', rpcauth_user2])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=', rpcauth_user1, rpcauth_user2])
+
+ self.log.info('Check -norpcauth disables previous -rpcauth params')
+ self.restart_node(0, extra_args=[rpcauth_user1, rpcauth_user2, '-norpcauth'])
+ assert_equal(401, call_with_auth(self.nodes[0], 'user1', 'bitcoin').status)
+ assert_equal(401, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
+ self.stop_node(0)
+
self.log.info('Check that failure to write cookie file will abort the node gracefully')
(self.nodes[0].chain_path / ".cookie.tmp").mkdir()
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 5c2fa28a31..705b8e8fe5 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2015-2022 The Bitcoin Core developers
+# Copyright (c) 2015-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for manipulating blocks and transactions."""
@@ -74,7 +74,7 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl
block.nVersion = version or tmpl.get('version') or VERSIONBITS_LAST_OLD_BLOCK_VERSION
block.nTime = ntime or tmpl.get('curtime') or int(time.time() + 600)
block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10)
- if tmpl and not tmpl.get('bits') is None:
+ if tmpl and tmpl.get('bits') is not None:
block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0]
else:
block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index b73566b0e9..60ca9269a5 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -43,6 +43,11 @@ from .util import (
)
BITCOIND_PROC_WAIT_TIMEOUT = 60
+# The size of the blocks xor key
+# from InitBlocksdirXorKey::xor_key.size()
+NUM_XOR_BYTES = 8
+# The null blocks key (all 0s)
+NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES)
class FailedToStartError(Exception):
@@ -466,6 +471,14 @@ class TestNode():
return self.chain_path / "blocks"
@property
+ def blocks_key_path(self) -> Path:
+ return self.blocks_path / "xor.dat"
+
+ def read_xor_key(self) -> bytes:
+ with open(self.blocks_key_path, "rb") as xor_f:
+ return xor_f.read(NUM_XOR_BYTES)
+
+ @property
def wallets_path(self) -> Path:
return self.chain_path / "wallets"
diff --git a/test/functional/test_framework/test_shell.py b/test/functional/test_framework/test_shell.py
index 09ccec28a1..e232430507 100644
--- a/test/functional/test_framework/test_shell.py
+++ b/test/functional/test_framework/test_shell.py
@@ -2,9 +2,11 @@
# Copyright (c) 2019-2022 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+import pathlib
from test_framework.test_framework import BitcoinTestFramework
+
class TestShell:
"""Wrapper Class for BitcoinTestFramework.
@@ -67,7 +69,13 @@ class TestShell:
# This implementation enforces singleton pattern, and will return the
# previously initialized instance if available
if not TestShell.instance:
- TestShell.instance = TestShell.__TestShell()
+ # BitcoinTestFramework instances are supposed to be constructed with the path
+ # of the calling test in order to find shared data like configuration and the
+ # cache. Since TestShell is meant for interactive use, there is no concrete
+ # test; passing a dummy name is fine though, as only the containing directory
+ # is relevant for successful initialization.
+ tests_directory = pathlib.Path(__file__).resolve().parent.parent
+ TestShell.instance = TestShell.__TestShell(tests_directory / "testshell_dummy.py")
TestShell.instance.running = False
return TestShell.instance
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index c3bc861cf6..ce68de7eaa 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -5,7 +5,7 @@
"""Helpful routines for regression testing."""
from base64 import b64encode
-from decimal import Decimal, ROUND_DOWN
+from decimal import Decimal
from subprocess import CalledProcessError
import hashlib
import inspect
@@ -21,7 +21,9 @@ import time
from . import coverage
from .authproxy import AuthServiceProxy, JSONRPCException
from collections.abc import Callable
-from typing import Optional
+from typing import Optional, Union
+
+SATOSHI_PRECISION = Decimal('0.00000001')
logger = logging.getLogger("TestFramework.utils")
@@ -261,8 +263,9 @@ def get_fee(tx_size, feerate_btc_kvb):
return target_fee_sat / Decimal(1e8) # Return result in BTC
-def satoshi_round(amount):
- return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
+def satoshi_round(amount: Union[int, float, str], *, rounding: str) -> Decimal:
+ """Rounds a Decimal amount to the nearest satoshi using the specified rounding mode."""
+ return Decimal(amount).quantize(SATOSHI_PRECISION, rounding=rounding)
def wait_until_helper_internal(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
@@ -515,12 +518,6 @@ def check_node_connections(*, node, num_in, num_out):
assert_equal(info["connections_out"], num_out)
-def read_xor_key(*, node):
- with open(node.blocks_path / "xor.dat", "rb") as xor_f:
- NUM_XOR_BYTES = 8 # From InitBlocksdirXorKey::xor_key.size()
- return xor_f.read(NUM_XOR_BYTES)
-
-
# Transaction/Block functions
#############################
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index b85bf1c668..3297a10699 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -406,6 +406,7 @@ BASE_SCRIPTS = [
'feature_shutdown.py',
'wallet_migration.py',
'p2p_ibd_txrelay.py',
+ 'p2p_seednode.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]
@@ -521,15 +522,24 @@ def main():
test_list += BASE_SCRIPTS
# Remove the test cases that the user has explicitly asked to exclude.
+ # The user can specify a test case with or without the .py extension.
if args.exclude:
- exclude_tests = [test.split('.py')[0] for test in args.exclude.split(',')]
- for exclude_test in exclude_tests:
- # Remove <test_name>.py and <test_name>.py --arg from the test list
- exclude_list = [test for test in test_list if test.split('.py')[0] == exclude_test]
+ def print_warning_missing_test(test_name):
+ print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], test_name))
+ def remove_tests(exclude_list):
+ if not exclude_list:
+ print_warning_missing_test(exclude_test)
for exclude_item in exclude_list:
test_list.remove(exclude_item)
- if not exclude_list:
- print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test))
+
+ exclude_tests = [test.strip() for test in args.exclude.split(",")]
+ for exclude_test in exclude_tests:
+ # A space in the name indicates it has arguments such as "wallet_basic.py --descriptors"
+ if ' ' in exclude_test:
+ remove_tests([test for test in test_list if test.replace('.py', '') == exclude_test.replace('.py', '')])
+ else:
+ # Exclude all variants of a test
+ remove_tests([test for test in test_list if test.split('.py')[0] == exclude_test.split('.py')[0]])
if args.filter:
test_list = list(filter(re.compile(args.filter).search, test_list))
@@ -560,7 +570,6 @@ def main():
run_tests(
test_list=test_list,
- src_dir=config["environment"]["SRCDIR"],
build_dir=config["environment"]["BUILDDIR"],
tmpdir=tmpdir,
jobs=args.jobs,
@@ -572,7 +581,7 @@ def main():
results_filepath=results_filepath,
)
-def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None):
+def run_tests(*, test_list, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None):
args = args or []
# Warn if bitcoind is already running
@@ -595,7 +604,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
print(f"{BOLD[1]}WARNING!{BOLD[0]} There may be insufficient free space in {tmpdir} to run the Bitcoin functional test suite. "
f"Running the test suite with fewer than {min_space // (1024 * 1024)} MB of free space might cause tests to fail.")
- tests_dir = src_dir + '/test/functional/'
+ tests_dir = f"{build_dir}/test/functional/"
# This allows `test_runner.py` to work from an out-of-source build directory using a symlink,
# a hard link or a copy on any platform. See https://github.com/bitcoin/bitcoin/pull/27561.
sys.path.append(tests_dir)
diff --git a/test/functional/tool_signet_miner.py b/test/functional/tool_signet_miner.py
index bdefb92ae6..67fb5c9f94 100755
--- a/test/functional/tool_signet_miner.py
+++ b/test/functional/tool_signet_miner.py
@@ -57,6 +57,7 @@ class SignetMinerTest(BitcoinTestFramework):
f'--grind-cmd={self.options.bitcoinutil} grind',
'--nbits=1d00ffff',
f'--set-block-time={int(time.time())}',
+ '--poolnum=99',
], check=True, stderr=subprocess.STDOUT)
assert_equal(node.getblockcount(), 1)
diff --git a/test/functional/wallet_assumeutxo.py b/test/functional/wallet_assumeutxo.py
index 0bce2f137c..a5025a64e7 100755
--- a/test/functional/wallet_assumeutxo.py
+++ b/test/functional/wallet_assumeutxo.py
@@ -93,7 +93,7 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info(
f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
- dump_output = n0.dumptxoutset('utxos.dat')
+ dump_output = n0.dumptxoutset('utxos.dat', "latest")
assert_equal(
dump_output['txoutset_hash'],
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index 6a69377c63..84c07b6a28 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -16,6 +16,7 @@ variants.
and test the values returned."""
import concurrent.futures
+import time
from test_framework.authproxy import JSONRPCException
from test_framework.blocktools import COINBASE_MATURITY
@@ -708,5 +709,56 @@ class ImportDescriptorsTest(BitcoinTestFramework):
assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance())
+ self.log.info("Multipath descriptors")
+ self.nodes[1].createwallet(wallet_name="multipath", descriptors=True, blank=True)
+ w_multipath = self.nodes[1].get_wallet_rpc("multipath")
+ self.nodes[1].createwallet(wallet_name="multipath_split", descriptors=True, blank=True)
+ w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split")
+ timestamp = int(time.time())
+
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": "now",
+ "label": "some label"},
+ success=False,
+ error_code=-8,
+ error_message="Multipath descriptors should not have a label",
+ wallet=w_multipath)
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": timestamp,
+ "internal": True},
+ success=False,
+ error_code=-5,
+ error_message="Cannot have multipath descriptor while also specifying \'internal\'",
+ wallet=w_multipath)
+
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": timestamp},
+ success=True,
+ wallet=w_multipath)
+
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/10/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": timestamp},
+ success=True,
+ wallet=w_multisplit)
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/20/0/*)"),
+ "active": True,
+ "range": 10,
+ "internal": True,
+ "timestamp": timestamp},
+ success=True,
+ wallet=w_multisplit)
+ for _ in range(0, 10):
+ assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32"))
+ assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32"))
+ assert_equal(sorted(w_multipath.listdescriptors()["descriptors"], key=lambda x: x["desc"]), sorted(w_multisplit.listdescriptors()["descriptors"], key=lambda x: x["desc"]))
+
if __name__ == '__main__':
ImportDescriptorsTest(__file__).main()
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 42b470bb97..3ce794dc2f 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -896,6 +896,43 @@ class ImportMultiTest(BitcoinTestFramework):
)
assert result[0]['success']
+ self.log.info("Multipath descriptors")
+ self.nodes[1].createwallet(wallet_name="multipath", blank=True, disable_private_keys=True)
+ w_multipath = self.nodes[1].get_wallet_rpc("multipath")
+ self.nodes[1].createwallet(wallet_name="multipath_split", blank=True, disable_private_keys=True)
+ w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split")
+
+ res = w_multipath.importmulti([{"desc": descsum_create(f"wpkh({xpub}/<10;20>/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "timestamp": "now",
+ "internal": True}])
+ assert_equal(res[0]["success"], False)
+ assert_equal(res[0]["error"]["code"], -5)
+ assert_equal(res[0]["error"]["message"], "Cannot have multipath descriptor while also specifying 'internal'")
+
+ res = w_multipath.importmulti([{"desc": descsum_create(f"wpkh({xpub}/<10;20>/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "timestamp": "now"}])
+ assert_equal(res[0]["success"], True)
+
+ res = w_multisplit.importmulti([{"desc": descsum_create(f"wpkh({xpub}/10/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "timestamp": "now"}])
+ assert_equal(res[0]["success"], True)
+ res = w_multisplit.importmulti([{"desc": descsum_create(f"wpkh({xpub}/20/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "internal": True,
+ "timestamp": timestamp}])
+ assert_equal(res[0]["success"], True)
+
+ for _ in range(0, 9):
+ assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32"))
+ assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32"))
+
if __name__ == '__main__':
ImportMultiTest(__file__).main()
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index 7d1d244dff..ef3f925ee8 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -231,7 +231,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
assert b'\x07hdchain' in new_kvs
hd_chain = new_kvs[b'\x07hdchain']
assert_equal(28, len(hd_chain))
- hd_chain_version, external_counter, seed_id = struct.unpack('<iI20s', hd_chain)
+ hd_chain_version, _external_counter, seed_id = struct.unpack('<iI20s', hd_chain)
assert_equal(1, hd_chain_version)
seed_id = bytearray(seed_id)
seed_id.reverse()
@@ -258,7 +258,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
new_kvs = dump_bdb_kv(node_master_wallet)
hd_chain = new_kvs[b'\x07hdchain']
assert_equal(32, len(hd_chain))
- hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
+ hd_chain_version, _external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
assert_equal(2, hd_chain_version)
assert_equal(0, internal_counter)
seed_id = bytearray(seed_id)
@@ -284,7 +284,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
new_kvs = dump_bdb_kv(node_master_wallet)
hd_chain = new_kvs[b'\x07hdchain']
assert_equal(32, len(hd_chain))
- hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
+ hd_chain_version, _external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
assert_equal(2, hd_chain_version)
assert_equal(2, internal_counter)
# The next addresses are HD and should be on different HD chains (the one remaining key in each pool should have been flushed)
@@ -301,8 +301,8 @@ class UpgradeWalletTest(BitcoinTestFramework):
new_kvs = dump_bdb_kv(node_master_wallet)
for k, old_v in old_kvs.items():
if k.startswith(b'\x07keymeta'):
- new_ver, new_create_time, new_kp_str, new_seed_id, new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k]))
- old_ver, old_create_time, old_kp_str, old_seed_id, old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v))
+ new_ver, new_create_time, new_kp_str, new_seed_id, _new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k]))
+ old_ver, old_create_time, old_kp_str, old_seed_id, _old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v))
assert_equal(10, old_ver)
if old_kp_str == b"": # imported things that don't have keymeta (i.e. imported coinbase privkeys) won't be upgraded
assert_equal(new_kvs[k], old_v)