aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/functional/README.md11
-rw-r--r--test/functional/data/invalid_txs.py2
-rwxr-xr-xtest/functional/example_test.py3
-rwxr-xr-xtest/functional/feature_asmap.py1
-rwxr-xr-xtest/functional/feature_assumevalid.py30
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py134
-rwxr-xr-xtest/functional/feature_config_args.py58
-rwxr-xr-xtest/functional/feature_dbcrash.py1
-rwxr-xr-xtest/functional/feature_filelock.py22
-rwxr-xr-xtest/functional/feature_includeconf.py1
-rwxr-xr-xtest/functional/feature_notifications.py34
-rwxr-xr-xtest/functional/feature_proxy.py4
-rwxr-xr-xtest/functional/feature_taproot.py1
-rwxr-xr-xtest/functional/feature_utxo_set_hash.py86
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py2
-rwxr-xr-xtest/functional/interface_zmq.py78
-rwxr-xr-xtest/functional/mempool_persist.py6
-rwxr-xr-xtest/functional/p2p_add_connections.py1
-rwxr-xr-xtest/functional/p2p_addr_relay.py1
-rwxr-xr-xtest/functional/p2p_blocksonly.py1
-rwxr-xr-xtest/functional/p2p_filter.py1
-rwxr-xr-xtest/functional/p2p_getaddr_caching.py1
-rwxr-xr-xtest/functional/p2p_invalid_locator.py1
-rwxr-xr-xtest/functional/p2p_message_capture.py76
-rwxr-xr-xtest/functional/p2p_timeouts.py6
-rwxr-xr-xtest/functional/p2p_tx_download.py1
-rwxr-xr-xtest/functional/rpc_blockchain.py12
-rwxr-xr-xtest/functional/rpc_estimatefee.py1
-rwxr-xr-xtest/functional/rpc_help.py64
-rwxr-xr-xtest/functional/rpc_invalid_address_message.py78
-rwxr-xr-xtest/functional/rpc_net.py13
-rwxr-xr-xtest/functional/rpc_psbt.py1
-rwxr-xr-xtest/functional/rpc_uptime.py5
-rw-r--r--test/functional/test-shell.md2
-rw-r--r--test/functional/test_framework/bdb.py1
-rw-r--r--test/functional/test_framework/key.py6
-rwxr-xr-xtest/functional/test_framework/messages.py2
-rw-r--r--test/functional/test_framework/script.py6
-rwxr-xr-xtest/functional/test_framework/test_framework.py42
-rwxr-xr-xtest/functional/test_runner.py75
-rwxr-xr-xtest/functional/tool_wallet.py11
-rwxr-xr-xtest/functional/wallet_avoidreuse.py1
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_createwallet.py1
-rwxr-xr-xtest/functional/wallet_descriptor.py13
-rwxr-xr-xtest/functional/wallet_listdescriptors.py67
-rwxr-xr-xtest/functional/wallet_multiwallet.py4
-rwxr-xr-xtest/functional/wallet_send.py101
-rwxr-xr-xtest/functional/wallet_upgradewallet.py1
-rwxr-xr-xtest/functional/wallet_watchonly.py1
-rwxr-xr-xtest/fuzz/test_runner.py40
-rwxr-xr-xtest/lint/check-rpc-mappings.py162
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh1
-rwxr-xr-xtest/lint/lint-includes.sh6
-rwxr-xr-xtest/lint/lint-python-dead-code.sh23
-rw-r--r--test/sanitizer_suppressions/tsan1
-rw-r--r--test/sanitizer_suppressions/ubsan60
57 files changed, 923 insertions, 443 deletions
diff --git a/test/functional/README.md b/test/functional/README.md
index 2d04413eb2..d830ba0334 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -63,10 +63,13 @@ don't have test cases for.
- Avoid stop-starting the nodes multiple times during the test if possible. A
stop-start takes several seconds, so doing it several times blows up the
runtime of the test.
-- Set the `self.setup_clean_chain` variable in `set_test_params()` to control whether
- or not to use the cached data directories. The cached data directories
- contain a 200-block pre-mined blockchain and wallets for four nodes. Each node
- has 25 mature blocks (25x50=1250 BTC) in its wallet.
+- Set the `self.setup_clean_chain` variable in `set_test_params()` to `True` to
+ initialize an empty blockchain and start from the Genesis block, rather than
+ load a premined blockchain from cache with the default value of `False`. The
+ cached data directories contain a 200-block pre-mined blockchain with the
+ spendable mining rewards being split between four nodes. Each node has 25
+ mature block subsidies (25x50=1250 BTC) in its wallet. Using them is much more
+ efficient than mining blocks in your test.
- When calling RPCs with lots of arguments, consider using named keyword
arguments instead of positional arguments to make the intent of the call
clear to readers.
diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py
index 6e72db1d96..fab921ef19 100644
--- a/test/functional/data/invalid_txs.py
+++ b/test/functional/data/invalid_txs.py
@@ -57,7 +57,7 @@ class BadTxTemplate:
__metaclass__ = abc.ABCMeta
# The expected error code given by bitcoind upon submission of the tx.
- reject_reason = "" # type: Optional[str]
+ reject_reason: Optional[str] = ""
# Only specified if it differs from mempool acceptance error.
block_reject_reason = ""
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 97f24e1b6e..a0eb213a78 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -76,6 +76,9 @@ class ExampleTest(BitcoinTestFramework):
"""Override test parameters for your individual test.
This method must be overridden and num_nodes must be explicitly set."""
+ # By default every test loads a pre-mined chain of 200 blocks from cache.
+ # Set setup_clean_chain to True to skip this and start from the Genesis
+ # block.
self.setup_clean_chain = True
self.num_nodes = 3
# Use self.extra_args to change command-line arguments for the nodes
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py
index 2c6553fbe2..5fcecb4882 100755
--- a/test/functional/feature_asmap.py
+++ b/test/functional/feature_asmap.py
@@ -36,7 +36,6 @@ def expected_messages(filename):
class AsmapTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def test_without_asmap_arg(self):
diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py
index 603d7f5d3b..1a148f04f4 100755
--- a/test/functional/feature_assumevalid.py
+++ b/test/functional/feature_assumevalid.py
@@ -29,9 +29,11 @@ Start three nodes:
block 200. node2 will reject block 102 since it's assumed valid, but it
isn't buried by at least two weeks' work.
"""
-import time
-from test_framework.blocktools import (create_block, create_coinbase)
+from test_framework.blocktools import (
+ create_block,
+ create_coinbase,
+)
from test_framework.key import ECKey
from test_framework.messages import (
CBlockHeader,
@@ -79,24 +81,6 @@ class AssumeValidTest(BitcoinTestFramework):
assert not p2p_conn.is_connected
break
- def assert_blockchain_height(self, node, height):
- """Wait until the blockchain is no longer advancing and verify it's reached the expected height."""
- last_height = node.getblock(node.getbestblockhash())['height']
- timeout = 10
- while True:
- time.sleep(0.25)
- current_height = node.getblock(node.getbestblockhash())['height']
- if current_height != last_height:
- last_height = current_height
- if timeout < 0:
- assert False, "blockchain too short after timeout: %d" % current_height
- timeout - 0.25
- continue
- elif current_height > height:
- assert False, "blockchain too long: %d" % current_height
- elif current_height == height:
- break
-
def run_test(self):
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
@@ -177,7 +161,8 @@ class AssumeValidTest(BitcoinTestFramework):
# Send blocks to node0. Block 102 will be rejected.
self.send_blocks_until_disconnected(p2p0)
- self.assert_blockchain_height(self.nodes[0], 101)
+ self.wait_until(lambda: self.nodes[0].getblockcount() >= 101)
+ assert_equal(self.nodes[0].getblockcount(), 101)
# Send all blocks to node1. All blocks will be accepted.
for i in range(2202):
@@ -188,7 +173,8 @@ class AssumeValidTest(BitcoinTestFramework):
# Send blocks to node2. Block 102 will be rejected.
self.send_blocks_until_disconnected(p2p2)
- self.assert_blockchain_height(self.nodes[2], 101)
+ self.wait_until(lambda: self.nodes[2].getblockcount() >= 101)
+ assert_equal(self.nodes[2].getblockcount(), 101)
if __name__ == '__main__':
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index b161c71a85..e6a53b52db 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -354,73 +354,75 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
hdkeypath = v17_info["hdkeypath"]
pubkey = v17_info["pubkey"]
- # Copy the 0.16 wallet to the last Bitcoin Core version and open it:
- shutil.copyfile(
- os.path.join(node_v16_wallets_dir, "wallets/u1_v16"),
- os.path.join(node_master_wallets_dir, "u1_v16")
- )
- load_res = node_master.loadwallet("u1_v16")
- # Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054
- assert_equal(load_res['warning'], '')
- wallet = node_master.get_wallet_rpc("u1_v16")
- info = wallet.getaddressinfo(v16_addr)
- descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + v16_pubkey + ")"
- assert_equal(info["desc"], descsum_create(descriptor))
-
- # Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
- os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16"))
- shutil.copyfile(
- os.path.join(node_master_wallets_dir, "u1_v16"),
- os.path.join(node_v16_wallets_dir, "wallets/u1_v16")
- )
- self.start_node(-1, extra_args=["-wallet=u1_v16"])
- wallet = node_v16.get_wallet_rpc("u1_v16")
- info = wallet.validateaddress(v16_addr)
- assert_equal(info, v16_info)
-
- # Copy the 0.17 wallet to the last Bitcoin Core version and open it:
- node_v17.unloadwallet("u1_v17")
- shutil.copytree(
- os.path.join(node_v17_wallets_dir, "u1_v17"),
- os.path.join(node_master_wallets_dir, "u1_v17")
- )
- node_master.loadwallet("u1_v17")
- wallet = node_master.get_wallet_rpc("u1_v17")
- info = wallet.getaddressinfo(address)
- descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")"
- assert_equal(info["desc"], descsum_create(descriptor))
-
- # Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it
- node_master.unloadwallet("u1_v17")
- shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17"))
- shutil.copytree(
- os.path.join(node_master_wallets_dir, "u1_v17"),
- os.path.join(node_v17_wallets_dir, "u1_v17")
- )
- node_v17.loadwallet("u1_v17")
- wallet = node_v17.get_wallet_rpc("u1_v17")
- info = wallet.getaddressinfo(address)
- assert_equal(info, v17_info)
-
- # Copy the 0.19 wallet to the last Bitcoin Core version and open it:
- shutil.copytree(
- os.path.join(node_v19_wallets_dir, "w1_v19"),
- os.path.join(node_master_wallets_dir, "w1_v19")
- )
- node_master.loadwallet("w1_v19")
- wallet = node_master.get_wallet_rpc("w1_v19")
- assert wallet.getaddressinfo(address_18075)["solvable"]
+ if self.is_bdb_compiled():
+ # Old wallets are BDB and will only work if BDB is compiled
+ # Copy the 0.16 wallet to the last Bitcoin Core version and open it:
+ shutil.copyfile(
+ os.path.join(node_v16_wallets_dir, "wallets/u1_v16"),
+ os.path.join(node_master_wallets_dir, "u1_v16")
+ )
+ load_res = node_master.loadwallet("u1_v16")
+ # Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054
+ assert_equal(load_res['warning'], '')
+ wallet = node_master.get_wallet_rpc("u1_v16")
+ info = wallet.getaddressinfo(v16_addr)
+ descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + v16_pubkey + ")"
+ assert_equal(info["desc"], descsum_create(descriptor))
+
+ # Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
+ os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16"))
+ shutil.copyfile(
+ os.path.join(node_master_wallets_dir, "u1_v16"),
+ os.path.join(node_v16_wallets_dir, "wallets/u1_v16")
+ )
+ self.start_node(-1, extra_args=["-wallet=u1_v16"])
+ wallet = node_v16.get_wallet_rpc("u1_v16")
+ info = wallet.validateaddress(v16_addr)
+ assert_equal(info, v16_info)
- # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it
- node_master.unloadwallet("w1_v19")
- shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19"))
- shutil.copytree(
- os.path.join(node_master_wallets_dir, "w1_v19"),
- os.path.join(node_v19_wallets_dir, "w1_v19")
- )
- node_v19.loadwallet("w1_v19")
- wallet = node_v19.get_wallet_rpc("w1_v19")
- assert wallet.getaddressinfo(address_18075)["solvable"]
+ # Copy the 0.17 wallet to the last Bitcoin Core version and open it:
+ node_v17.unloadwallet("u1_v17")
+ shutil.copytree(
+ os.path.join(node_v17_wallets_dir, "u1_v17"),
+ os.path.join(node_master_wallets_dir, "u1_v17")
+ )
+ node_master.loadwallet("u1_v17")
+ wallet = node_master.get_wallet_rpc("u1_v17")
+ info = wallet.getaddressinfo(address)
+ descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")"
+ assert_equal(info["desc"], descsum_create(descriptor))
+
+ # Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it
+ node_master.unloadwallet("u1_v17")
+ shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17"))
+ shutil.copytree(
+ os.path.join(node_master_wallets_dir, "u1_v17"),
+ os.path.join(node_v17_wallets_dir, "u1_v17")
+ )
+ node_v17.loadwallet("u1_v17")
+ wallet = node_v17.get_wallet_rpc("u1_v17")
+ info = wallet.getaddressinfo(address)
+ assert_equal(info, v17_info)
+
+ # Copy the 0.19 wallet to the last Bitcoin Core version and open it:
+ shutil.copytree(
+ os.path.join(node_v19_wallets_dir, "w1_v19"),
+ os.path.join(node_master_wallets_dir, "w1_v19")
+ )
+ node_master.loadwallet("w1_v19")
+ wallet = node_master.get_wallet_rpc("w1_v19")
+ assert wallet.getaddressinfo(address_18075)["solvable"]
+
+ # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it
+ node_master.unloadwallet("w1_v19")
+ shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19"))
+ shutil.copytree(
+ os.path.join(node_master_wallets_dir, "w1_v19"),
+ os.path.join(node_v19_wallets_dir, "w1_v19")
+ )
+ node_v19.loadwallet("w1_v19")
+ wallet = node_v19.get_wallet_rpc("w1_v19")
+ assert wallet.getaddressinfo(address_18075)["solvable"]
if __name__ == '__main__':
BackwardsCompatibilityTest().main()
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 2445b6d977..573760a8cb 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -5,6 +5,7 @@
"""Test various command line arguments and configuration file parameters."""
import os
+import time
from test_framework.test_framework import BitcoinTestFramework
from test_framework import util
@@ -147,11 +148,68 @@ class ConfArgsTest(BitcoinTestFramework):
self.start_node(0, extra_args=['-nonetworkactive=1'])
self.stop_node(0)
+ def test_seed_peers(self):
+ self.log.info('Test seed peers')
+ default_data_dir = self.nodes[0].datadir
+
+ # No peers.dat exists and -dnsseed=1
+ # We expect the node will use DNS Seeds, but Regtest mode has 0 DNS seeds
+ # So after 60 seconds, the node should fallback to fixed seeds (this is a slow test)
+ assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
+ start = int(time.time())
+ with self.nodes[0].assert_debug_log(expected_msgs=[
+ "Loaded 0 addresses from peers.dat",
+ "0 addresses found from DNS seeds"]):
+ self.start_node(0, extra_args=['-dnsseed=1 -mocktime={}'.format(start)])
+ with self.nodes[0].assert_debug_log(expected_msgs=[
+ "Adding fixed seeds as 60 seconds have passed and addrman is empty"]):
+ self.nodes[0].setmocktime(start + 65)
+ self.stop_node(0)
+
+ # No peers.dat exists and -dnsseed=0
+ # We expect the node will fallback immediately to fixed seeds
+ assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
+ start = time.time()
+ with self.nodes[0].assert_debug_log(expected_msgs=[
+ "Loaded 0 addresses from peers.dat",
+ "DNS seeding disabled",
+ "Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n"]):
+ self.start_node(0, extra_args=['-dnsseed=0'])
+ assert time.time() - start < 60
+ self.stop_node(0)
+
+ # No peers.dat exists and dns seeds are disabled.
+ # We expect the node will not add fixed seeds when explicitly disabled.
+ assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
+ start = time.time()
+ with self.nodes[0].assert_debug_log(expected_msgs=[
+ "Loaded 0 addresses from peers.dat",
+ "DNS seeding disabled",
+ "Fixed seeds are disabled"]):
+ self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=0'])
+ assert time.time() - start < 60
+ self.stop_node(0)
+
+ # No peers.dat exists and -dnsseed=0, but a -addnode is provided
+ # We expect the node will allow 60 seconds prior to using fixed seeds
+ assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
+ start = int(time.time())
+ with self.nodes[0].assert_debug_log(expected_msgs=[
+ "Loaded 0 addresses from peers.dat",
+ "DNS seeding disabled"]):
+ self.start_node(0, extra_args=['-dnsseed=0', '-addnode=fakenodeaddr -mocktime={}'.format(start)])
+ with self.nodes[0].assert_debug_log(expected_msgs=[
+ "Adding fixed seeds as 60 seconds have passed and addrman is empty"]):
+ self.nodes[0].setmocktime(start + 65)
+ self.stop_node(0)
+
+
def run_test(self):
self.stop_node(0)
self.test_log_buffer()
self.test_args_log()
+ self.test_seed_peers()
self.test_networkactive()
self.test_config_file_parser()
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index f9ece244fb..2b56bc78f5 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -49,7 +49,6 @@ from test_framework.util import (
class ChainstateWriteCrashTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
- self.setup_clean_chain = False
self.rpc_timeout = 480
self.supports_cli = False
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index 7de9a589be..2798d11b0a 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -4,6 +4,8 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Check that it's not possible to start a second bitcoind instance using the same datadir or wallet."""
import os
+import random
+import string
from test_framework.test_framework import BitcoinTestFramework
from test_framework.test_node import ErrorMatch
@@ -27,11 +29,21 @@ class FilelockTest(BitcoinTestFramework):
self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg)
if self.is_wallet_compiled():
- self.nodes[0].createwallet(self.default_wallet_name)
- wallet_dir = os.path.join(datadir, 'wallets')
- self.log.info("Check that we can't start a second bitcoind instance using the same wallet")
- expected_msg = "Error: Error initializing wallet database environment"
- self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + self.default_wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
+ def check_wallet_filelock(descriptors):
+ wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)])
+ self.nodes[0].createwallet(wallet_name=wallet_name, descriptors=descriptors)
+ wallet_dir = os.path.join(datadir, 'wallets')
+ self.log.info("Check that we can't start a second bitcoind instance using the same wallet")
+ if descriptors:
+ expected_msg = "Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?"
+ else:
+ expected_msg = "Error: Error initializing wallet database environment"
+ self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
+
+ if self.is_bdb_compiled():
+ check_wallet_filelock(False)
+ if self.is_sqlite_compiled():
+ check_wallet_filelock(True)
if __name__ == '__main__':
FilelockTest().main()
diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py
index 6f1a0cd348..f22b7f266a 100755
--- a/test/functional/feature_includeconf.py
+++ b/test/functional/feature_includeconf.py
@@ -20,7 +20,6 @@ from test_framework.test_framework import BitcoinTestFramework
class IncludeConfTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def setup_chain(self):
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index f2313bac13..b068ce612c 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -5,11 +5,11 @@
"""Test the -alertnotify, -blocknotify and -walletnotify options."""
import os
-from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE, keyhash_to_p2pkh
+from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
+from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- hex_str_to_bytes,
)
# Linux allow all characters other than \x00
@@ -49,6 +49,31 @@ class NotificationsTest(BitcoinTestFramework):
super().setup_network()
def run_test(self):
+ if self.is_wallet_compiled():
+ # Setup the descriptors to be imported to the wallet
+ seed = "cTdGmKFWpbvpKQ7ejrdzqYT2hhjyb3GPHnLAK7wdi5Em67YLwSm9"
+ xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
+ desc_imports = [{
+ "desc": descsum_create("wpkh(" + xpriv + "/0/*)"),
+ "timestamp": 0,
+ "active": True,
+ "keypool": True,
+ },{
+ "desc": descsum_create("wpkh(" + xpriv + "/1/*)"),
+ "timestamp": 0,
+ "active": True,
+ "keypool": True,
+ "internal": True,
+ }]
+ # Make the wallets and import the descriptors
+ # Ensures that node 0 and node 1 share the same wallet for the conflicting transaction tests below.
+ for i, name in enumerate(self.wallet_names):
+ self.nodes[i].createwallet(wallet_name=name, descriptors=self.options.descriptors, blank=True, load_on_startup=True)
+ if self.options.descriptors:
+ self.nodes[i].importdescriptors(desc_imports)
+ else:
+ self.nodes[i].sethdseed(True, seed)
+
self.log.info("test -blocknotify")
block_count = 10
blocks = self.nodes[1].generatetoaddress(block_count, self.nodes[1].getnewaddress() if self.is_wallet_compiled() else ADDRESS_BCRT1_UNSPENDABLE)
@@ -84,11 +109,10 @@ class NotificationsTest(BitcoinTestFramework):
for tx_file in os.listdir(self.walletnotify_dir):
os.remove(os.path.join(self.walletnotify_dir, tx_file))
- # Conflicting transactions tests. Give node 0 same wallet seed as
- # node 1, generate spends from node 0, and check notifications
+ # Conflicting transactions tests.
+ # Generate spends from node 0, and check notifications
# triggered by node 1
self.log.info("test -walletnotify with conflicting transactions")
- self.nodes[0].sethdseed(seed=self.nodes[1].dumpprivkey(keyhash_to_p2pkh(hex_str_to_bytes(self.nodes[1].getwalletinfo()['hdseedid'])[::-1])))
self.nodes[0].rescanblockchain()
self.nodes[0].generatetoaddress(100, ADDRESS_BCRT1_UNSPENDABLE)
self.sync_blocks()
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index cd5eff9184..2983feaa0d 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -44,8 +44,8 @@ from test_framework.netutil import test_ipv6_local
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
-# Networks returned by RPC getpeerinfo, defined in src/netbase.cpp::GetNetworkName()
-NET_UNROUTABLE = "unroutable"
+# Networks returned by RPC getpeerinfo.
+NET_UNROUTABLE = "not_publicly_routable"
NET_IPV4 = "ipv4"
NET_IPV6 = "ipv6"
NET_ONION = "onion"
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index 6ee2b72c11..5027a9828f 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -517,7 +517,6 @@ def add_spender(spenders, *args, **kwargs):
def random_checksig_style(pubkey):
"""Creates a random CHECKSIG* tapscript that would succeed with only the valid signature on witness stack."""
- return bytes(CScript([pubkey, OP_CHECKSIG]))
opcode = random.choice([OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD])
if (opcode == OP_CHECKSIGVERIFY):
ret = CScript([pubkey, opcode, OP_1])
diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py
new file mode 100755
index 0000000000..6e6046d84d
--- /dev/null
+++ b/test/functional/feature_utxo_set_hash.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020-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 UTXO set hash value calculation in gettxoutsetinfo."""
+
+import struct
+
+from test_framework.blocktools import create_transaction
+from test_framework.messages import (
+ CBlock,
+ COutPoint,
+ FromHex,
+)
+from test_framework.muhash import MuHash3072
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+class UTXOSetHashTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def test_deterministic_hash_results(self):
+ self.log.info("Test deterministic UTXO set hash results")
+
+ # These depend on the setup_clean_chain option, the chain loaded from the cache
+ assert_equal(self.nodes[0].gettxoutsetinfo()['hash_serialized_2'], "b32ec1dda5a53cd025b95387aad344a801825fe46a60ff952ce26528f01d3be8")
+ assert_equal(self.nodes[0].gettxoutsetinfo("muhash")['muhash'], "dd5ad2a105c2d29495f577245c357409002329b9f4d6182c0af3dc2f462555c8")
+
+ def test_muhash_implementation(self):
+ self.log.info("Test MuHash implementation consistency")
+
+ node = self.nodes[0]
+
+ # Generate 100 blocks and remove the first since we plan to spend its
+ # coinbase
+ block_hashes = node.generate(100)
+ blocks = list(map(lambda block: FromHex(CBlock(), node.getblock(block, False)), block_hashes))
+ spending = blocks.pop(0)
+
+ # Create a spending transaction and mine a block which includes it
+ tx = create_transaction(node, spending.vtx[0].rehash(), node.getnewaddress(), amount=49)
+ txid = node.sendrawtransaction(hexstring=tx.serialize_with_witness().hex(), maxfeerate=0)
+
+ tx_block = node.generateblock(output=node.getnewaddress(), transactions=[txid])
+ blocks.append(FromHex(CBlock(), node.getblock(tx_block['hash'], False)))
+
+ # Serialize the outputs that should be in the UTXO set and add them to
+ # a MuHash object
+ muhash = MuHash3072()
+
+ for height, block in enumerate(blocks):
+ # The Genesis block coinbase is not part of the UTXO set and we
+ # spent the first mined block
+ height += 2
+
+ for tx in block.vtx:
+ for n, tx_out in enumerate(tx.vout):
+ coinbase = 1 if not tx.vin[0].prevout.hash else 0
+
+ # Skip witness commitment
+ if (coinbase and n > 0):
+ continue
+
+ data = COutPoint(int(tx.rehash(), 16), n).serialize()
+ data += struct.pack("<i", height * 2 + coinbase)
+ data += tx_out.serialize()
+
+ muhash.insert(data)
+
+ finalized = muhash.digest()
+ node_muhash = node.gettxoutsetinfo("muhash")['muhash']
+
+ assert_equal(finalized[::-1].hex(), node_muhash)
+
+ def run_test(self):
+ self.test_deterministic_hash_results()
+ self.test_muhash_implementation()
+
+
+if __name__ == '__main__':
+ UTXOSetHashTest().main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 1257dff1ae..2cf0ef2251 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -29,6 +29,8 @@ class TestBitcoinCli(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
+ if self.is_wallet_compiled():
+ self.requires_wallet = True
def skip_test_if_missing_module(self):
self.skip_if_no_cli()
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 946bfa51d4..d0967a9340 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -27,28 +27,31 @@ def hash256_reversed(byte_str):
class ZMQSubscriber:
def __init__(self, socket, topic):
- self.sequence = 0
+ self.sequence = None # no sequence number received yet
self.socket = socket
self.topic = topic
self.socket.setsockopt(zmq.SUBSCRIBE, self.topic)
- def receive(self):
+ # Receive message from publisher and verify that topic and sequence match
+ def _receive_from_publisher_and_check(self):
topic, body, seq = self.socket.recv_multipart()
# Topic should match the subscriber topic.
assert_equal(topic, self.topic)
# Sequence should be incremental.
- assert_equal(struct.unpack('<I', seq)[-1], self.sequence)
+ received_seq = struct.unpack('<I', seq)[-1]
+ if self.sequence is None:
+ self.sequence = received_seq
+ else:
+ assert_equal(received_seq, self.sequence)
self.sequence += 1
return body
+ def receive(self):
+ return self._receive_from_publisher_and_check()
+
def receive_sequence(self):
- topic, body, seq = self.socket.recv_multipart()
- # Topic should match the subscriber topic.
- assert_equal(topic, self.topic)
- # Sequence should be incremental.
- assert_equal(struct.unpack('<I', seq)[-1], self.sequence)
- self.sequence += 1
+ body = self._receive_from_publisher_and_check()
hash = body[:32].hex()
label = chr(body[32])
mempool_sequence = None if len(body) != 32+1+8 else struct.unpack("<Q", body[32+1:])[0]
@@ -62,6 +65,11 @@ class ZMQSubscriber:
class ZMQTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ if self.is_wallet_compiled():
+ self.requires_wallet = True
+ # This test isn't testing txn relay/timing, so set whitelist on the
+ # peers for instant txn relay. This speeds up the test run time 2-3x.
+ self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_py3_zmq()
@@ -82,23 +90,46 @@ class ZMQTest (BitcoinTestFramework):
# Restart node with the specified zmq notifications enabled, subscribe to
# all of them and return the corresponding ZMQSubscriber objects.
- def setup_zmq_test(self, services, recv_timeout=60, connect_nodes=False):
+ def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True):
subscribers = []
for topic, address in services:
socket = self.ctx.socket(zmq.SUB)
- socket.set(zmq.RCVTIMEO, recv_timeout*1000)
subscribers.append(ZMQSubscriber(socket, topic.encode()))
- self.restart_node(0, ["-zmqpub%s=%s" % (topic, address) for topic, address in services])
-
- if connect_nodes:
- self.connect_nodes(0, 1)
+ self.restart_node(0, ["-zmqpub%s=%s" % (topic, address) for topic, address in services] +
+ self.extra_args[0])
for i, sub in enumerate(subscribers):
sub.socket.connect(services[i][1])
- # Relax so that the subscribers are ready before publishing zmq messages
- sleep(0.2)
+ # Ensure that all zmq publisher notification interfaces are ready by
+ # running the following "sync up" procedure:
+ # 1. Generate a block on the node
+ # 2. Try to receive a notification on all subscribers
+ # 3. If all subscribers get a message within the timeout (1 second),
+ # we are done, otherwise repeat starting from step 1
+ for sub in subscribers:
+ sub.socket.set(zmq.RCVTIMEO, 1000)
+ while True:
+ self.nodes[0].generate(1)
+ recv_failed = False
+ for sub in subscribers:
+ try:
+ sub.receive()
+ except zmq.error.Again:
+ self.log.debug("Didn't receive sync-up notification, trying again.")
+ recv_failed = True
+ if not recv_failed:
+ self.log.debug("ZMQ sync-up completed, all subscribers are ready.")
+ break
+
+ # set subscriber's desired timeout for the test
+ for sub in subscribers:
+ sub.socket.set(zmq.RCVTIMEO, recv_timeout*1000)
+
+ self.connect_nodes(0, 1)
+ if sync_blocks:
+ self.sync_blocks()
return subscribers
@@ -108,9 +139,7 @@ class ZMQTest (BitcoinTestFramework):
self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
address = 'tcp://127.0.0.1:28332'
- subs = self.setup_zmq_test(
- [(topic, address) for topic in ["hashblock", "hashtx", "rawblock", "rawtx"]],
- connect_nodes=True)
+ subs = self.setup_zmq_test([(topic, address) for topic in ["hashblock", "hashtx", "rawblock", "rawtx"]])
hashblock = subs[0]
hashtx = subs[1]
@@ -187,6 +216,7 @@ class ZMQTest (BitcoinTestFramework):
hashblock, hashtx = self.setup_zmq_test(
[(topic, address) for topic in ["hashblock", "hashtx"]],
recv_timeout=2) # 2 second timeout to check end of notifications
+ self.disconnect_nodes(0, 1)
# Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications
payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
@@ -235,6 +265,7 @@ class ZMQTest (BitcoinTestFramework):
"""
self.log.info("Testing 'sequence' publisher")
[seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")])
+ self.disconnect_nodes(0, 1)
# Mempool sequence number starts at 1
seq_num = 1
@@ -385,7 +416,7 @@ class ZMQTest (BitcoinTestFramework):
return
self.log.info("Testing 'mempool sync' usage of sequence notifier")
- [seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")], connect_nodes=True)
+ [seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")])
# In-memory counter, should always start at 1
next_mempool_seq = self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"]
@@ -485,10 +516,13 @@ class ZMQTest (BitcoinTestFramework):
def test_multiple_interfaces(self):
# Set up two subscribers with different addresses
+ # (note that after the reorg test, syncing would fail due to different
+ # chain lengths on node0 and node1; for this test we only need node0, so
+ # we can disable syncing blocks on the setup)
subscribers = self.setup_zmq_test([
("hashblock", "tcp://127.0.0.1:28334"),
("hashblock", "tcp://127.0.0.1:28335"),
- ])
+ ], sync_blocks=False)
# Generate 1 block in nodes[0] and receive all notifications
self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index 70cd4ebb3b..752b925b92 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -69,6 +69,8 @@ class MempoolPersistTest(BitcoinTestFramework):
assert_equal(len(self.nodes[0].getrawmempool()), 5)
assert_equal(len(self.nodes[1].getrawmempool()), 5)
+ total_fee_old = self.nodes[0].getmempoolinfo()['total_fee']
+
self.log.debug("Prioritize a transaction on node0")
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
assert_equal(fees['base'], fees['modified'])
@@ -76,6 +78,10 @@ class MempoolPersistTest(BitcoinTestFramework):
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])
+ self.log.info('Check the total base fee is unchanged after prioritisetransaction')
+ assert_equal(total_fee_old, self.nodes[0].getmempoolinfo()['total_fee'])
+ assert_equal(total_fee_old, sum(v['fees']['base'] for k, v in self.nodes[0].getrawmempool(verbose=True).items()))
+
tx_creation_time = self.nodes[0].getmempoolentry(txid=last_txid)['time']
assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower)
assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time)
diff --git a/test/functional/p2p_add_connections.py b/test/functional/p2p_add_connections.py
index a63c3a3287..a04ba5db2d 100755
--- a/test/functional/p2p_add_connections.py
+++ b/test/functional/p2p_add_connections.py
@@ -17,7 +17,6 @@ def check_node_connections(*, node, num_in, num_out):
class P2PAddConnections(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 2
def setup_network(self):
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index 91fbd722cf..69821763bd 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -46,7 +46,6 @@ class AddrReceiver(P2PInterface):
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def run_test(self):
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index c592ab52b1..6584efae79 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -15,7 +15,6 @@ from test_framework.util import assert_equal
class P2PBlocksOnly(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
self.extra_args = [["-blocksonly"]]
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py
index 642a217047..458e5235b6 100755
--- a/test/functional/p2p_filter.py
+++ b/test/functional/p2p_filter.py
@@ -81,7 +81,6 @@ class P2PBloomFilter(P2PInterface):
class FilterTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
self.extra_args = [[
'-peerbloomfilters',
diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py
index 2b75ad5175..d375af6fe1 100755
--- a/test/functional/p2p_getaddr_caching.py
+++ b/test/functional/p2p_getaddr_caching.py
@@ -41,7 +41,6 @@ class AddrReceiver(P2PInterface):
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def run_test(self):
diff --git a/test/functional/p2p_invalid_locator.py b/test/functional/p2p_invalid_locator.py
index e4fc9fd178..f884cf90ff 100755
--- a/test/functional/p2p_invalid_locator.py
+++ b/test/functional/p2p_invalid_locator.py
@@ -13,7 +13,6 @@ from test_framework.test_framework import BitcoinTestFramework
class InvalidLocatorTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.setup_clean_chain = False
def run_test(self):
node = self.nodes[0] # convenience reference to the node
diff --git a/test/functional/p2p_message_capture.py b/test/functional/p2p_message_capture.py
new file mode 100755
index 0000000000..080b2d93ad
--- /dev/null
+++ b/test/functional/p2p_message_capture.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 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 per-peer message capture capability.
+
+Additionally, the output of contrib/message-capture/message-capture-parser.py should be verified manually.
+"""
+
+import glob
+from io import BytesIO
+import os
+
+from test_framework.p2p import P2PDataStore, MESSAGEMAP
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+TIME_SIZE = 8
+LENGTH_SIZE = 4
+MSGTYPE_SIZE = 12
+
+def mini_parser(dat_file):
+ """Parse a data file created by CaptureMessage.
+
+ From the data file we'll only check the structure.
+
+ We won't care about things like:
+ - Deserializing the payload of the message
+ - This is managed by the deserialize methods in test_framework.messages
+ - The order of the messages
+ - There's no reason why we can't, say, change the order of the messages in the handshake
+ - Message Type
+ - We can add new message types
+
+ We're ignoring these because they're simply too brittle to test here.
+ """
+ with open(dat_file, 'rb') as f_in:
+ # This should have at least one message in it
+ assert(os.fstat(f_in.fileno()).st_size >= TIME_SIZE + LENGTH_SIZE + MSGTYPE_SIZE)
+ while True:
+ tmp_header_raw = f_in.read(TIME_SIZE + LENGTH_SIZE + MSGTYPE_SIZE)
+ if not tmp_header_raw:
+ break
+ tmp_header = BytesIO(tmp_header_raw)
+ tmp_header.read(TIME_SIZE) # skip the timestamp field
+ raw_msgtype = tmp_header.read(MSGTYPE_SIZE)
+ msgtype: bytes = raw_msgtype.split(b'\x00', 1)[0]
+ remainder = raw_msgtype.split(b'\x00', 1)[1]
+ assert(len(msgtype) > 0)
+ assert(msgtype in MESSAGEMAP)
+ assert(len(remainder) == 0 or not remainder.decode().isprintable())
+ length: int = int.from_bytes(tmp_header.read(LENGTH_SIZE), "little")
+ data = f_in.read(length)
+ assert_equal(len(data), length)
+
+
+
+class MessageCaptureTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.extra_args = [["-capturemessages"]]
+ self.setup_clean_chain = True
+
+ def run_test(self):
+ capturedir = os.path.join(self.nodes[0].datadir, "regtest/message_capture")
+ # Connect a node so that the handshake occurs
+ self.nodes[0].add_p2p_connection(P2PDataStore())
+ self.nodes[0].disconnect_p2ps()
+ recv_file = glob.glob(os.path.join(capturedir, "*/msgs_recv.dat"))[0]
+ mini_parser(recv_file)
+ sent_file = glob.glob(os.path.join(capturedir, "*/msgs_sent.dat"))[0]
+ mini_parser(sent_file)
+
+
+if __name__ == '__main__':
+ MessageCaptureTest().main()
diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py
index 47832b04bf..a7e240dcfa 100755
--- a/test/functional/p2p_timeouts.py
+++ b/test/functional/p2p_timeouts.py
@@ -74,9 +74,9 @@ class TimeoutsTest(BitcoinTestFramework):
no_version_node.send_message(msg_ping())
expected_timeout_logs = [
- "version handshake timeout from 0",
- "socket no message in first 3 seconds, 1 0 from 1",
- "socket no message in first 3 seconds, 0 0 from 2",
+ "version handshake timeout peer=0",
+ "socket no message in first 3 seconds, 1 0 peer=1",
+ "socket no message in first 3 seconds, 0 0 peer=2",
]
with self.nodes[0].assert_debug_log(expected_msgs=expected_timeout_logs):
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 8a751c6b54..4bf96cb0e6 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -56,7 +56,6 @@ MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + INBOUND_PEER_TX_DELAY + TXID_RE
class TxDownloadTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 2
def test_tx_requests(self):
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 99be6b7b8e..84ca1b99c2 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -268,6 +268,18 @@ class BlockchainTest(BitcoinTestFramework):
res5 = node.gettxoutsetinfo(hash_type='none')
assert 'hash_serialized_2' not in res5
+ # hash_type muhash should return a different UTXO set hash.
+ res6 = node.gettxoutsetinfo(hash_type='muhash')
+ assert 'muhash' in res6
+ assert(res['hash_serialized_2'] != res6['muhash'])
+
+ # muhash should not be included in gettxoutset unless requested.
+ for r in [res, res2, res3, res4, res5]:
+ assert 'muhash' not in r
+
+ # Unknown hash_type raises an error
+ assert_raises_rpc_error(-8, "foohash is not a valid hash_type", node.gettxoutsetinfo, "foohash")
+
def _test_getblockheader(self):
node = self.nodes[0]
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 81862ac69e..51b7efb4c3 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -14,7 +14,6 @@ from test_framework.util import assert_raises_rpc_error
class EstimateFeeTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def run_test(self):
diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py
index c83157a843..1eefd109f8 100755
--- a/test/functional/rpc_help.py
+++ b/test/functional/rpc_help.py
@@ -7,7 +7,39 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
+from collections import defaultdict
import os
+import re
+
+
+def parse_string(s):
+ assert s[0] == '"'
+ assert s[-1] == '"'
+ return s[1:-1]
+
+
+def process_mapping(fname):
+ """Find and parse conversion table in implementation file `fname`."""
+ cmds = []
+ in_rpcs = False
+ with open(fname, "r", encoding="utf8") as f:
+ for line in f:
+ line = line.rstrip()
+ if not in_rpcs:
+ if line == 'static const CRPCConvertParam vRPCConvertParams[] =':
+ in_rpcs = True
+ else:
+ if line.startswith('};'):
+ in_rpcs = False
+ elif '{' in line and '"' in line:
+ m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line)
+ assert m, 'No match to table expression: %s' % line
+ name = parse_string(m.group(1))
+ idx = int(m.group(2))
+ argname = parse_string(m.group(3))
+ cmds.append((name, idx, argname))
+ assert not in_rpcs and cmds
+ return cmds
class HelpRpcTest(BitcoinTestFramework):
@@ -16,11 +48,43 @@ class HelpRpcTest(BitcoinTestFramework):
self.supports_cli = False
def run_test(self):
+ self.test_client_conversion_table()
self.test_categories()
self.dump_help()
if self.is_wallet_compiled():
self.wallet_help()
+ def test_client_conversion_table(self):
+ file_conversion_table = os.path.join(self.config["environment"]["SRCDIR"], 'src', 'rpc', 'client.cpp')
+ mapping_client = process_mapping(file_conversion_table)
+ # Ignore echojson in client table
+ mapping_client = [m for m in mapping_client if m[0] != 'echojson']
+
+ mapping_server = self.nodes[0].help("dump_all_command_conversions")
+ # Filter all RPCs whether they need conversion
+ mapping_server_conversion = [tuple(m[:3]) for m in mapping_server if not m[3]]
+
+ # Only check if all RPC methods have been compiled (i.e. wallet is enabled)
+ if self.is_wallet_compiled() and sorted(mapping_client) != sorted(mapping_server_conversion):
+ raise AssertionError("RPC client conversion table ({}) and RPC server named arguments mismatch!\n{}".format(
+ file_conversion_table,
+ set(mapping_client).symmetric_difference(mapping_server_conversion),
+ ))
+
+ # Check for conversion difference by argument name.
+ # It is preferable for API consistency that arguments with the same name
+ # have the same conversion, so bin by argument name.
+ all_methods_by_argname = defaultdict(list)
+ converts_by_argname = defaultdict(list)
+ for m in mapping_server:
+ all_methods_by_argname[m[2]].append(m[0])
+ converts_by_argname[m[2]].append(m[3])
+
+ for argname, convert in converts_by_argname.items():
+ if all(convert) != any(convert):
+ # Only allow dummy to fail consistency check
+ assert argname == 'dummy', ('WARNING: conversion mismatch for argument named %s (%s)' % (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname]))))
+
def test_categories(self):
node = self.nodes[0]
diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py
new file mode 100755
index 0000000000..469d6bdb05
--- /dev/null
+++ b/test/functional/rpc_invalid_address_message.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 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 error messages for 'getaddressinfo' and 'validateaddress' RPC commands."""
+
+from test_framework.test_framework import BitcoinTestFramework
+
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
+
+BECH32_VALID = 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv'
+BECH32_INVALID_SIZE = 'bcrt1sqqpl9r5c'
+BECH32_INVALID_PREFIX = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
+
+BASE58_VALID = 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn'
+BASE58_INVALID_PREFIX = '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem'
+
+INVALID_ADDRESS = 'asfah14i8fajz0123f'
+
+class InvalidAddressErrorMessageTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def test_validateaddress(self):
+ node = self.nodes[0]
+
+ # Bech32
+ info = node.validateaddress(BECH32_INVALID_SIZE)
+ assert not info['isvalid']
+ assert_equal(info['error'], 'Invalid Bech32 address data size')
+
+ info = node.validateaddress(BECH32_INVALID_PREFIX)
+ assert not info['isvalid']
+ assert_equal(info['error'], 'Invalid prefix for Bech32 address')
+
+ info = node.validateaddress(BECH32_VALID)
+ assert info['isvalid']
+ assert 'error' not in info
+
+ # Base58
+ info = node.validateaddress(BASE58_INVALID_PREFIX)
+ assert not info['isvalid']
+ assert_equal(info['error'], 'Invalid prefix for Base58-encoded address')
+
+ info = node.validateaddress(BASE58_VALID)
+ assert info['isvalid']
+ assert 'error' not in info
+
+ # Invalid address format
+ info = node.validateaddress(INVALID_ADDRESS)
+ assert not info['isvalid']
+ assert_equal(info['error'], 'Invalid address format')
+
+ def test_getaddressinfo(self):
+ node = self.nodes[0]
+
+ assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE)
+
+ assert_raises_rpc_error(-5, "Invalid prefix for Bech32 address", node.getaddressinfo, BECH32_INVALID_PREFIX)
+
+ assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", node.getaddressinfo, BASE58_INVALID_PREFIX)
+
+ assert_raises_rpc_error(-5, "Invalid address format", node.getaddressinfo, INVALID_ADDRESS)
+
+ def run_test(self):
+ self.test_validateaddress()
+ self.test_getaddressinfo()
+
+
+if __name__ == '__main__':
+ InvalidAddressErrorMessageTest().main()
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index de0b7f303f..2d41963beb 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -25,6 +25,7 @@ from test_framework.util import (
assert_raises_rpc_error,
p2p_port,
)
+from test_framework.wallet import MiniWallet
def assert_net_servicesnames(servicesflag, servicenames):
@@ -48,6 +49,9 @@ class NetTest(BitcoinTestFramework):
self.supports_cli = False
def run_test(self):
+ # We need miniwallet to make a transaction
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.generate(1)
# Get out of IBD for the minfeefilter and getpeerinfo tests.
self.nodes[0].generate(101)
@@ -74,8 +78,7 @@ class NetTest(BitcoinTestFramework):
def test_getpeerinfo(self):
self.log.info("Test getpeerinfo")
# Create a few getpeerinfo last_block/last_transaction values.
- if self.is_wallet_compiled():
- self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ self.wallet.send_self_transfer(from_node=self.nodes[0]) # Make a transaction so we can see it in the getpeerinfo results
self.nodes[1].generate(1)
self.sync_all()
time_now = int(time.time())
@@ -101,6 +104,9 @@ class NetTest(BitcoinTestFramework):
assert_equal(peer_info[1][0]['connection_type'], 'manual')
assert_equal(peer_info[1][1]['connection_type'], 'inbound')
+ # Check dynamically generated networks list in getpeerinfo help output.
+ assert "(ipv4, ipv6, onion, not_publicly_routable)" in self.nodes[0].help("getpeerinfo")
+
def test_getnettotals(self):
self.log.info("Test getnettotals")
# Test getnettotals and getpeerinfo by doing a ping. The bytes
@@ -149,6 +155,9 @@ class NetTest(BitcoinTestFramework):
for info in network_info:
assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"])
+ # Check dynamically generated networks list in getnetworkinfo help output.
+ assert "(ipv4, ipv6, onion)" in self.nodes[0].help("getnetworkinfo")
+
def test_getaddednodeinfo(self):
self.log.info("Test getaddednodeinfo")
assert_equal(self.nodes[0].getaddednodeinfo(), [])
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index b364077a9a..ed6abaed78 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -24,7 +24,6 @@ MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
class PSBTTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 3
self.extra_args = [
["-walletrbf=1"],
diff --git a/test/functional/rpc_uptime.py b/test/functional/rpc_uptime.py
index e86f91b1d0..6177970872 100755
--- a/test/functional/rpc_uptime.py
+++ b/test/functional/rpc_uptime.py
@@ -10,6 +10,7 @@ Test corresponds to code in rpc/server.cpp.
import time
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_raises_rpc_error
class UptimeTest(BitcoinTestFramework):
@@ -18,8 +19,12 @@ class UptimeTest(BitcoinTestFramework):
self.setup_clean_chain = True
def run_test(self):
+ self._test_negative_time()
self._test_uptime()
+ def _test_negative_time(self):
+ assert_raises_rpc_error(-8, "Mocktime can not be negative: -1.", self.nodes[0].setmocktime, -1)
+
def _test_uptime(self):
wait_time = 10
self.nodes[0].setmocktime(int(time.time() + wait_time))
diff --git a/test/functional/test-shell.md b/test/functional/test-shell.md
index f6ea9ef682..b8e899d675 100644
--- a/test/functional/test-shell.md
+++ b/test/functional/test-shell.md
@@ -178,7 +178,7 @@ can be called after the TestShell is shut down.
| `num_nodes` | `1` | Sets the number of initialized bitcoind processes. |
| `perf` | False | Profiles running nodes with `perf` for the duration of the test if set to `True`. |
| `rpc_timeout` | `60` | Sets the RPC server timeout for the underlying bitcoind processes. |
-| `setup_clean_chain` | `False` | Initializes an empty blockchain by default. A 199-block-long chain is initialized if set to `True`. |
+| `setup_clean_chain` | `False` | A 200-block-long chain is initialized from cache by default. Instead, `setup_clean_chain` initializes an empty blockchain if set to `True`. |
| `randomseed` | Random Integer | `TestShell.options.randomseed` is a member of `TestShell` which can be accessed during a test to seed a random generator. User can override default with a constant value for reproducible test runs. |
| `supports_cli` | `False` | Whether the bitcoin-cli utility is compiled and available for the test. |
| `tmpdir` | `"/var/folders/.../"` | Sets directory for test logs. Will be deleted upon a successful test run unless `nocleanup` is set to `True` |
diff --git a/test/functional/test_framework/bdb.py b/test/functional/test_framework/bdb.py
index 9de358aa0a..97b9c1d6d0 100644
--- a/test/functional/test_framework/bdb.py
+++ b/test/functional/test_framework/bdb.py
@@ -51,7 +51,6 @@ def dump_leaf_page(data):
page_info['pgno'] = pgno
page_info['prev_pgno'] = prev_pgno
page_info['next_pgno'] = next_pgno
- page_info['entries'] = entries
page_info['hf_offset'] = hf_offset
page_info['level'] = level
page_info['pg_type'] = pg_type
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index e0cbab45ce..26526e35fa 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -20,10 +20,6 @@ def TaggedHash(tag, data):
ss += data
return hashlib.sha256(ss).digest()
-def xor_bytes(b0, b1):
- assert len(b0) == len(b1)
- return bytes(x ^ y for (x, y) in zip(b0, b1))
-
def jacobi_symbol(n, k):
"""Compute the Jacobi symbol of n modulo k
@@ -510,7 +506,7 @@ class TestFrameworkKey(unittest.TestCase):
if pubkey is not None:
keys[privkey] = pubkey
for msg in byte_arrays: # test every combination of message, signing key, verification key
- for sign_privkey, sign_pubkey in keys.items():
+ for sign_privkey, _ in keys.items():
sig = sign_schnorr(sign_privkey, msg)
for verify_privkey, verify_pubkey in keys.items():
if verify_privkey == sign_privkey:
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index e5743d8192..561d1813c1 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -1273,7 +1273,7 @@ class msg_block:
# for cases where a user needs tighter control over what is sent over the wire
# note that the user must supply the name of the msgtype, and the data
class msg_generic:
- __slots__ = ("msgtype", "data")
+ __slots__ = ("data")
def __init__(self, msgtype, data=None):
self.msgtype = msgtype
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index be0e9f24e2..c35533698c 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -29,8 +29,6 @@ MAX_SCRIPT_ELEMENT_SIZE = 520
LOCKTIME_THRESHOLD = 500000000
ANNEX_TAG = 0x50
-OPCODE_NAMES = {} # type: Dict[CScriptOp, str]
-
LEAF_VERSION_TAPSCRIPT = 0xc0
def hash160(s):
@@ -47,7 +45,6 @@ def bn2vch(v):
# Serialize to bytes
return encoded_v.to_bytes(n_bytes, 'little')
-_opcode_instances = [] # type: List[CScriptOp]
class CScriptOp(int):
"""A single script opcode"""
__slots__ = ()
@@ -111,6 +108,9 @@ class CScriptOp(int):
_opcode_instances.append(super().__new__(cls, n))
return _opcode_instances[n]
+OPCODE_NAMES: Dict[CScriptOp, str] = {}
+_opcode_instances: List[CScriptOp] = []
+
# Populate opcode instance table
for n in range(0xff + 1):
CScriptOp(n)
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 7c5317480c..70a9798449 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -18,6 +18,7 @@ import sys
import tempfile
import time
+from typing import List
from .authproxy import JSONRPCException
from . import coverage
from .p2p import NetworkThread
@@ -89,14 +90,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
This class also contains various public and private helper methods."""
- chain = None # type: str
- setup_clean_chain = None # type: bool
-
def __init__(self):
"""Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method"""
- self.chain = 'regtest'
- self.setup_clean_chain = False
- self.nodes = []
+ self.chain: str = 'regtest'
+ self.setup_clean_chain: bool = False
+ self.nodes: List[TestNode] = []
self.network_thread = None
self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond
self.supports_cli = True
@@ -110,6 +108,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# skipped. If list is truncated, wallet creation is skipped and keys
# are not imported.
self.wallet_names = None
+ # By default the wallet is not required. Set to true by skip_if_no_wallet().
+ # When False, we ignore wallet_names regardless of what it is.
+ self.requires_wallet = False
self.set_test_params()
assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes
if self.options.timeout_factor == 0 :
@@ -186,15 +187,30 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts')
group = parser.add_mutually_exclusive_group()
- group.add_argument("--descriptors", default=False, action="store_true",
+ group.add_argument("--descriptors", action='store_const', const=True,
help="Run test using a descriptor wallet", dest='descriptors')
- group.add_argument("--legacy-wallet", default=False, action="store_false",
+ group.add_argument("--legacy-wallet", action='store_const', const=False,
help="Run test using legacy wallets", dest='descriptors')
self.add_options(parser)
self.options = parser.parse_args()
self.options.previous_releases_path = previous_releases_path
+ config = configparser.ConfigParser()
+ config.read_file(open(self.options.configfile))
+ self.config = config
+
+ if self.options.descriptors is None:
+ # Prefer BDB unless it isn't available
+ if self.is_bdb_compiled():
+ self.options.descriptors = False
+ elif self.is_sqlite_compiled():
+ self.options.descriptors = True
+ else:
+ # If neither are compiled, tests requiring a wallet will be skipped and the value of self.options.descriptors won't matter
+ # It still needs to exist and be None in order for tests to work however.
+ self.options.descriptors = None
+
def setup(self):
"""Call this method to start up the test framework object with options set."""
@@ -204,9 +220,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.options.cachedir = os.path.abspath(self.options.cachedir)
- config = configparser.ConfigParser()
- config.read_file(open(self.options.configfile))
- self.config = config
+ config = self.config
+
fname_bitcoind = os.path.join(
config["environment"]["BUILDDIR"],
"src",
@@ -379,7 +394,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
extra_args = self.extra_args
self.add_nodes(self.num_nodes, extra_args)
self.start_nodes()
- if self.is_wallet_compiled():
+ if self.requires_wallet:
self.import_deterministic_coinbase_privkeys()
if not self.setup_clean_chain:
for n in self.nodes:
@@ -771,10 +786,13 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def skip_if_no_wallet(self):
"""Skip the running test if wallet has not been compiled."""
+ self.requires_wallet = True
if not self.is_wallet_compiled():
raise SkipTest("wallet has not been compiled.")
if self.options.descriptors:
self.skip_if_no_sqlite()
+ else:
+ self.skip_if_no_bdb()
def skip_if_no_sqlite(self):
"""Skip the running test if sqlite has not been compiled."""
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 9bbf862568..354cacf95a 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -86,29 +86,29 @@ EXTENDED_SCRIPTS = [
BASE_SCRIPTS = [
# Scripts that are run by default.
# Longest test should go first, to favor running tests in parallel
- 'wallet_hd.py',
+ 'wallet_hd.py --legacy-wallet',
'wallet_hd.py --descriptors',
- 'wallet_backup.py',
+ 'wallet_backup.py --legacy-wallet',
'wallet_backup.py --descriptors',
# vv Tests less than 5m vv
'mining_getblocktemplate_longpoll.py',
'feature_maxuploadtarget.py',
'feature_block.py',
- 'rpc_fundrawtransaction.py',
+ 'rpc_fundrawtransaction.py --legacy-wallet',
'rpc_fundrawtransaction.py --descriptors',
'p2p_compactblocks.py',
'feature_segwit.py --legacy-wallet',
# vv Tests less than 2m vv
- 'wallet_basic.py',
+ 'wallet_basic.py --legacy-wallet',
'wallet_basic.py --descriptors',
- 'wallet_labels.py',
+ 'wallet_labels.py --legacy-wallet',
'wallet_labels.py --descriptors',
'p2p_segwit.py',
'p2p_timeouts.py',
'p2p_tx_download.py',
'mempool_updatefromblock.py',
'wallet_dump.py --legacy-wallet',
- 'wallet_listtransactions.py',
+ 'wallet_listtransactions.py --legacy-wallet',
'wallet_listtransactions.py --descriptors',
'feature_taproot.py',
# vv Tests less than 60s vv
@@ -116,28 +116,29 @@ BASE_SCRIPTS = [
'wallet_importmulti.py --legacy-wallet',
'mempool_limit.py',
'rpc_txoutproof.py',
- 'wallet_listreceivedby.py',
+ 'wallet_listreceivedby.py --legacy-wallet',
'wallet_listreceivedby.py --descriptors',
- 'wallet_abandonconflict.py',
+ 'wallet_abandonconflict.py --legacy-wallet',
'wallet_abandonconflict.py --descriptors',
'feature_csv_activation.py',
- 'rpc_rawtransaction.py',
+ 'rpc_rawtransaction.py --legacy-wallet',
'rpc_rawtransaction.py --descriptors',
- 'wallet_address_types.py',
+ 'wallet_address_types.py --legacy-wallet',
'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
'feature_reindex.py',
'feature_abortnode.py',
# vv Tests less than 30s vv
- 'wallet_keypool_topup.py',
+ 'wallet_keypool_topup.py --legacy-wallet',
'wallet_keypool_topup.py --descriptors',
'feature_fee_estimation.py',
'interface_zmq.py',
+ 'rpc_invalid_address_message.py',
'interface_bitcoin_cli.py',
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
- 'tool_wallet.py',
+ 'tool_wallet.py --legacy-wallet',
'tool_wallet.py --descriptors',
'wallet_txn_clone.py',
'wallet_txn_clone.py --segwit',
@@ -145,14 +146,14 @@ BASE_SCRIPTS = [
'rpc_misc.py',
'interface_rest.py',
'mempool_spend_coinbase.py',
- 'wallet_avoidreuse.py',
+ 'wallet_avoidreuse.py --legacy-wallet',
'wallet_avoidreuse.py --descriptors',
'mempool_reorg.py',
'mempool_persist.py',
- 'wallet_multiwallet.py',
+ 'wallet_multiwallet.py --legacy-wallet',
'wallet_multiwallet.py --descriptors',
'wallet_multiwallet.py --usecli',
- 'wallet_createwallet.py',
+ 'wallet_createwallet.py --legacy-wallet',
'wallet_createwallet.py --usecli',
'wallet_createwallet.py --descriptors',
'wallet_watchonly.py --legacy-wallet',
@@ -160,27 +161,27 @@ BASE_SCRIPTS = [
'wallet_reorgsrestore.py',
'interface_http.py',
'interface_rpc.py',
- 'rpc_psbt.py',
+ 'rpc_psbt.py --legacy-wallet',
'rpc_psbt.py --descriptors',
'rpc_users.py',
'rpc_whitelist.py',
'feature_proxy.py',
- 'rpc_signrawtransaction.py',
+ 'rpc_signrawtransaction.py --legacy-wallet',
'rpc_signrawtransaction.py --descriptors',
- 'wallet_groups.py',
+ 'wallet_groups.py --legacy-wallet',
'p2p_addrv2_relay.py',
'wallet_groups.py --descriptors',
'p2p_disconnect_ban.py',
'rpc_decodescript.py',
'rpc_blockchain.py',
'rpc_deprecated.py',
- 'wallet_disable.py',
+ 'wallet_disable.py --legacy-wallet',
'wallet_disable.py --descriptors',
'p2p_addr_relay.py',
'p2p_getaddr_caching.py',
'p2p_getdata.py',
'rpc_net.py',
- 'wallet_keypool.py',
+ 'wallet_keypool.py --legacy-wallet',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py --descriptors',
'p2p_nobloomfilter_messages.py',
@@ -194,69 +195,72 @@ BASE_SCRIPTS = [
'p2p_invalid_tx.py',
'feature_assumevalid.py',
'example_test.py',
- 'wallet_txn_doublespend.py',
+ 'wallet_txn_doublespend.py --legacy-wallet',
'wallet_txn_doublespend.py --descriptors',
- 'feature_backwards_compatibility.py',
+ 'feature_backwards_compatibility.py --legacy-wallet',
'feature_backwards_compatibility.py --descriptors',
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
'rpc_getblockfilter.py',
'rpc_invalidateblock.py',
+ 'feature_utxo_set_hash.py',
'feature_rbf.py',
'mempool_packages.py',
'mempool_package_onemore.py',
- 'rpc_createmultisig.py',
+ 'rpc_createmultisig.py --legacy-wallet',
'rpc_createmultisig.py --descriptors',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
- 'wallet_importprunedfunds.py',
+ 'wallet_importprunedfunds.py --legacy-wallet',
'wallet_importprunedfunds.py --descriptors',
'p2p_leak_tx.py',
'p2p_eviction.py',
'rpc_signmessage.py',
'rpc_generateblock.py',
'rpc_generate.py',
- 'wallet_balance.py',
+ 'wallet_balance.py --legacy-wallet',
'wallet_balance.py --descriptors',
- 'feature_nulldummy.py',
+ 'feature_nulldummy.py --legacy-wallet',
'feature_nulldummy.py --descriptors',
'mempool_accept.py',
'mempool_expiry.py',
'wallet_import_rescan.py --legacy-wallet',
'wallet_import_with_label.py --legacy-wallet',
'wallet_importdescriptors.py --descriptors',
- 'wallet_upgradewallet.py',
+ 'wallet_upgradewallet.py --legacy-wallet',
'rpc_bind.py --ipv4',
'rpc_bind.py --ipv6',
'rpc_bind.py --nonloopback',
'mining_basic.py',
'feature_signet.py',
- 'wallet_bumpfee.py',
+ 'wallet_bumpfee.py --legacy-wallet',
'wallet_bumpfee.py --descriptors',
'wallet_implicitsegwit.py --legacy-wallet',
'rpc_named_arguments.py',
- 'wallet_listsinceblock.py',
+ 'wallet_listsinceblock.py --legacy-wallet',
'wallet_listsinceblock.py --descriptors',
+ 'wallet_listdescriptors.py --descriptors',
'p2p_leak.py',
- 'wallet_encryption.py',
+ 'wallet_encryption.py --legacy-wallet',
'wallet_encryption.py --descriptors',
'feature_dersig.py',
'feature_cltv.py',
'rpc_uptime.py',
- 'wallet_resendwallettransactions.py',
+ 'wallet_resendwallettransactions.py --legacy-wallet',
'wallet_resendwallettransactions.py --descriptors',
- 'wallet_fallbackfee.py',
+ 'wallet_fallbackfee.py --legacy-wallet',
'wallet_fallbackfee.py --descriptors',
'rpc_dumptxoutset.py',
'feature_minchainwork.py',
'rpc_estimatefee.py',
'rpc_getblockstats.py',
- 'wallet_create_tx.py',
- 'wallet_send.py',
+ 'wallet_create_tx.py --legacy-wallet',
+ 'wallet_send.py --legacy-wallet',
+ 'wallet_send.py --descriptors',
'wallet_create_tx.py --descriptors',
'p2p_fingerprint.py',
'feature_uacomment.py',
- 'wallet_coinbase_category.py',
+ 'wallet_coinbase_category.py --legacy-wallet',
'wallet_coinbase_category.py --descriptors',
'feature_filelock.py',
'feature_loadblock.py',
@@ -264,6 +268,7 @@ BASE_SCRIPTS = [
'p2p_add_connections.py',
'p2p_unrequested_blocks.py',
'p2p_blockfilters.py',
+ 'p2p_message_capture.py',
'feature_includeconf.py',
'feature_asmap.py',
'mempool_unbroadcast.py',
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index 8a1af24dcf..28103793df 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -183,11 +183,13 @@ class ToolWalletTest(BitcoinTestFramework):
def test_invalid_tool_commands_and_args(self):
self.log.info('Testing that various invalid commands raise with specific error messages')
- self.assert_raises_tool_error('Invalid command: foo', 'foo')
+ self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'foo'", 'foo')
# `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`.
- self.assert_raises_tool_error('Invalid command: help', 'help')
- self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
+ self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'help'", 'help')
+ self.assert_raises_tool_error('Error: Additional arguments provided (create). Methods do not take arguments. Please refer to `-help`.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
+ self.assert_raises_tool_error('No method provided. Run `bitcoin-wallet -help` for valid methods.')
+ self.assert_raises_tool_error('Wallet name must be provided when creating a new wallet.', 'create')
locked_dir = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets")
error = 'Error initializing wallet database environment "{}"!'.format(locked_dir)
if self.options.descriptors:
@@ -348,7 +350,8 @@ class ToolWalletTest(BitcoinTestFramework):
self.log.info('Checking createfromdump')
self.do_tool_createfromdump("load", "wallet.dump")
- self.do_tool_createfromdump("load-bdb", "wallet.dump", "bdb")
+ if self.is_bdb_compiled():
+ self.do_tool_createfromdump("load-bdb", "wallet.dump", "bdb")
if self.is_sqlite_compiled():
self.do_tool_createfromdump("load-sqlite", "wallet.dump", "sqlite")
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index 229c134a4b..bc4fa90e83 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -65,7 +65,6 @@ def assert_balances(node, mine):
class AvoidReuseTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 2
# This test isn't testing txn relay/timing, so set whitelist on the
# peers for instant txn relay. This speeds up the test run time 2-3x.
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 6bcb03e8ed..4a589f0393 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -586,7 +586,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
# Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py
- assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
+ assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
address_info = self.nodes[0].getaddressinfo("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
assert_equal(address_info['address'], "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
assert_equal(address_info["scriptPubKey"], "76a9144e3854046c7bd1594ac904e4793b6a45b36dea0988ac")
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
index cf3317121f..16a0a50b07 100755
--- a/test/functional/wallet_createwallet.py
+++ b/test/functional/wallet_createwallet.py
@@ -17,7 +17,6 @@ from test_framework.wallet_util import bytes_to_wif, generate_wif_key
class CreateWalletTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def skip_test_if_missing_module(self):
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index 1de41a5f96..c04a15a67c 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -23,11 +23,14 @@ class WalletDescriptorTest(BitcoinTestFramework):
self.skip_if_no_sqlite()
def run_test(self):
- # Make a legacy wallet and check it is BDB
- self.nodes[0].createwallet(wallet_name="legacy1", descriptors=False)
- wallet_info = self.nodes[0].getwalletinfo()
- assert_equal(wallet_info['format'], 'bdb')
- self.nodes[0].unloadwallet("legacy1")
+ if self.is_bdb_compiled():
+ # Make a legacy wallet and check it is BDB
+ self.nodes[0].createwallet(wallet_name="legacy1", descriptors=False)
+ wallet_info = self.nodes[0].getwalletinfo()
+ assert_equal(wallet_info['format'], 'bdb')
+ self.nodes[0].unloadwallet("legacy1")
+ else:
+ self.log.warning("Skipping BDB test")
# Make a descriptor wallet
self.log.info("Making a descriptor wallet")
diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py
new file mode 100755
index 0000000000..9f8c341bc7
--- /dev/null
+++ b/test/functional/wallet_listdescriptors.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-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 the listdescriptors RPC."""
+
+from test_framework.descriptors import (
+ descsum_create
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
+
+
+class ListDescriptorsTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+ self.skip_if_no_sqlite()
+
+ # do not create any wallet by default
+ def init_wallet(self, i):
+ return
+
+ def run_test(self):
+ node = self.nodes[0]
+ assert_raises_rpc_error(-18, 'No wallet is loaded.', node.listdescriptors)
+
+ self.log.info('Test that the command is not available for legacy wallets.')
+ node.createwallet(wallet_name='w1', descriptors=False)
+ assert_raises_rpc_error(-4, 'listdescriptors is not available for non-descriptor wallets', node.listdescriptors)
+
+ self.log.info('Test the command for empty descriptors wallet.')
+ node.createwallet(wallet_name='w2', blank=True, descriptors=True)
+ assert_equal(0, len(node.get_wallet_rpc('w2').listdescriptors()))
+
+ self.log.info('Test the command for a default descriptors wallet.')
+ node.createwallet(wallet_name='w3', descriptors=True)
+ result = node.get_wallet_rpc('w3').listdescriptors()
+ assert_equal(6, len(result))
+ assert_equal(6, len([d for d in result if d['active']]))
+ assert_equal(3, len([d for d in result if d['internal']]))
+ for item in result:
+ assert item['desc'] != ''
+ assert item['next'] == 0
+ assert item['range'] == [0, 0]
+ assert item['timestamp'] is not None
+
+ self.log.info('Test non-active non-range combo descriptor')
+ node.createwallet(wallet_name='w4', blank=True, descriptors=True)
+ wallet = node.get_wallet_rpc('w4')
+ wallet.importdescriptors([{
+ 'desc': descsum_create('combo(' + node.get_deterministic_priv_key().key + ')'),
+ 'timestamp': 1296688602,
+ }])
+ expected = [{'active': False,
+ 'desc': 'combo(0227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3f)#np574htj',
+ 'timestamp': 1296688602}]
+ assert_equal(expected, wallet.listdescriptors())
+
+
+if __name__ == '__main__':
+ ListDescriptorsTest().main()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index bb89e76a9a..bf24b9c7b3 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -304,12 +304,12 @@ class MultiWalletTest(BitcoinTestFramework):
if self.options.descriptors:
assert_raises_rpc_error(-4, "Wallet file verification failed. SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?", self.nodes[0].loadwallet, wallet_names[0])
else:
- assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
+ assert_raises_rpc_error(-35, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
# This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this
# Fail to load duplicate wallets by different ways (directory and filepath)
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat")
- assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat')
+ assert_raises_rpc_error(-35, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat')
# Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary
# Fail to load if one wallet is a copy of another
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 9835c5a2af..880341fdd9 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -8,6 +8,7 @@ from decimal import Decimal, getcontext
from itertools import product
from test_framework.authproxy import JSONRPCException
+from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -168,49 +169,91 @@ class WalletSendTest(BitcoinTestFramework):
self.nodes[1].createwallet(wallet_name="w1")
w1 = self.nodes[1].get_wallet_rpc("w1")
# w2 contains the private keys for w3
- self.nodes[1].createwallet(wallet_name="w2")
+ self.nodes[1].createwallet(wallet_name="w2", blank=True)
w2 = self.nodes[1].get_wallet_rpc("w2")
+ xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
+ xpub = "tpubD6NzVbkrYhZ4YkEfMbRJkQyZe7wTkbTNRECozCtJPtdLRn6cT1QKb8yHjwAPcAr26eHBFYs5iLiFFnCbwPRsncCKUKCfubHDMGKzMVcN1Jg"
+ if self.options.descriptors:
+ w2.importdescriptors([{
+ "desc": descsum_create("wpkh(" + xpriv + "/0/0/*)"),
+ "timestamp": "now",
+ "range": [0, 100],
+ "active": True
+ },{
+ "desc": descsum_create("wpkh(" + xpriv + "/0/1/*)"),
+ "timestamp": "now",
+ "range": [0, 100],
+ "active": True,
+ "internal": True
+ }])
+ else:
+ w2.sethdseed(True)
+
# w3 is a watch-only wallet, based on w2
self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)
w3 = self.nodes[1].get_wallet_rpc("w3")
- for _ in range(3):
- a2_receive = w2.getnewaddress()
- a2_change = w2.getrawchangeaddress() # doesn't actually use change derivation
- res = w3.importmulti([{
- "desc": w2.getaddressinfo(a2_receive)["desc"],
+ if self.options.descriptors:
+ # Match the privkeys in w2 for descriptors
+ res = w3.importdescriptors([{
+ "desc": descsum_create("wpkh(" + xpub + "/0/0/*)"),
"timestamp": "now",
+ "range": [0, 100],
"keypool": True,
+ "active": True,
"watchonly": True
},{
- "desc": w2.getaddressinfo(a2_change)["desc"],
+ "desc": descsum_create("wpkh(" + xpub + "/0/1/*)"),
"timestamp": "now",
+ "range": [0, 100],
"keypool": True,
+ "active": True,
"internal": True,
"watchonly": True
}])
assert_equal(res, [{"success": True}, {"success": True}])
- w0.sendtoaddress(a2_receive, 10) # fund w3
- self.nodes[0].generate(1)
- self.sync_blocks()
-
- # w4 has private keys enabled, but only contains watch-only keys (from w2)
- self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
- w4 = self.nodes[1].get_wallet_rpc("w4")
for _ in range(3):
a2_receive = w2.getnewaddress()
- res = w4.importmulti([{
- "desc": w2.getaddressinfo(a2_receive)["desc"],
- "timestamp": "now",
- "keypool": False,
- "watchonly": True
- }])
- assert_equal(res, [{"success": True}])
+ if not self.options.descriptors:
+ # Because legacy wallets use exclusively hardened derivation, we can't do a ranged import like we do for descriptors
+ a2_change = w2.getrawchangeaddress() # doesn't actually use change derivation
+ res = w3.importmulti([{
+ "desc": w2.getaddressinfo(a2_receive)["desc"],
+ "timestamp": "now",
+ "keypool": True,
+ "watchonly": True
+ },{
+ "desc": w2.getaddressinfo(a2_change)["desc"],
+ "timestamp": "now",
+ "keypool": True,
+ "internal": True,
+ "watchonly": True
+ }])
+ assert_equal(res, [{"success": True}, {"success": True}])
- w0.sendtoaddress(a2_receive, 10) # fund w4
+ w0.sendtoaddress(a2_receive, 10) # fund w3
self.nodes[0].generate(1)
self.sync_blocks()
+ if not self.options.descriptors:
+ # w4 has private keys enabled, but only contains watch-only keys (from w2)
+ # This is legacy wallet behavior only as descriptor wallets don't allow watchonly and non-watchonly things in the same wallet.
+ self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
+ w4 = self.nodes[1].get_wallet_rpc("w4")
+ for _ in range(3):
+ a2_receive = w2.getnewaddress()
+ res = w4.importmulti([{
+ "desc": w2.getaddressinfo(a2_receive)["desc"],
+ "timestamp": "now",
+ "keypool": False,
+ "watchonly": True
+ }])
+ assert_equal(res, [{"success": True}])
+
+ w0.sendtoaddress(a2_receive, 10) # fund w4
+ self.nodes[0].generate(1)
+ self.sync_blocks()
+
self.log.info("Send to address...")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True)
@@ -241,11 +284,15 @@ class WalletSendTest(BitcoinTestFramework):
res = w2.walletprocesspsbt(res["psbt"])
assert res["complete"]
- self.log.info("Create PSBT from wallet w4 with watch-only keys, sign with w2...")
- self.test_send(from_wallet=w4, to_wallet=w1, amount=1, expect_error=(-4, "Insufficient funds"))
- res = self.test_send(from_wallet=w4, to_wallet=w1, amount=1, include_watching=True, add_to_wallet=False)
- res = w2.walletprocesspsbt(res["psbt"])
- assert res["complete"]
+ if not self.options.descriptors:
+ # Descriptor wallets do not allow mixed watch-only and non-watch-only things in the same wallet.
+ # This is specifically testing that w4 ignores its own private keys and creates a psbt with send
+ # which is not something that needs to be tested in descriptor wallets.
+ self.log.info("Create PSBT from wallet w4 with watch-only keys, sign with w2...")
+ self.test_send(from_wallet=w4, to_wallet=w1, amount=1, expect_error=(-4, "Insufficient funds"))
+ res = self.test_send(from_wallet=w4, to_wallet=w1, amount=1, include_watching=True, add_to_wallet=False)
+ res = w2.walletprocesspsbt(res["psbt"])
+ assert res["complete"]
self.log.info("Create OP_RETURN...")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index d0bb6135a8..fbc0f995d2 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -57,6 +57,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ self.skip_if_no_bdb()
self.skip_if_no_previous_releases()
def setup_network(self):
diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py
index b0c41b2738..24799fe5f2 100755
--- a/test/functional/wallet_watchonly.py
+++ b/test/functional/wallet_watchonly.py
@@ -14,7 +14,6 @@ from test_framework.util import (
class CreateWalletWatchonlyTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = False
self.num_nodes = 1
def skip_test_if_missing_module(self):
diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py
index 3c743603bb..611061072f 100755
--- a/test/fuzz/test_runner.py
+++ b/test/fuzz/test_runner.py
@@ -14,6 +14,16 @@ import subprocess
import sys
+def get_fuzz_env(*, target, source_dir):
+ return {
+ 'FUZZ': target,
+ 'UBSAN_OPTIONS':
+ f'suppressions={source_dir}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1',
+ 'ASAN_OPTIONS': # symbolizer disabled due to https://github.com/google/sanitizers/issues/1364#issuecomment-761072085
+ 'symbolize=0:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1',
+ }
+
+
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
@@ -129,9 +139,7 @@ def main():
os.path.join(config["environment"]["BUILDDIR"], 'src', 'test', 'fuzz', 'fuzz'),
'-help=1',
],
- env={
- 'FUZZ': test_list_selection[0]
- },
+ env=get_fuzz_env(target=test_list_selection[0], source_dir=config['environment']['SRCDIR']),
timeout=20,
check=True,
stderr=subprocess.PIPE,
@@ -148,6 +156,7 @@ def main():
if args.generate:
return generate_corpus_seeds(
fuzz_pool=fuzz_pool,
+ src_dir=config['environment']['SRCDIR'],
build_dir=config["environment"]["BUILDDIR"],
seed_dir=args.seed_dir,
targets=test_list_selection,
@@ -158,6 +167,7 @@ def main():
fuzz_pool=fuzz_pool,
corpus=args.seed_dir,
test_list=test_list_selection,
+ src_dir=config['environment']['SRCDIR'],
build_dir=config["environment"]["BUILDDIR"],
merge_dir=args.m_dir,
)
@@ -167,12 +177,13 @@ def main():
fuzz_pool=fuzz_pool,
corpus=args.seed_dir,
test_list=test_list_selection,
+ src_dir=config['environment']['SRCDIR'],
build_dir=config["environment"]["BUILDDIR"],
use_valgrind=args.valgrind,
)
-def generate_corpus_seeds(*, fuzz_pool, build_dir, seed_dir, targets):
+def generate_corpus_seeds(*, fuzz_pool, src_dir, build_dir, seed_dir, targets):
"""Generates new corpus seeds.
Run {targets} without input, and outputs the generated corpus seeds to
@@ -186,9 +197,7 @@ def generate_corpus_seeds(*, fuzz_pool, build_dir, seed_dir, targets):
' '.join(command),
subprocess.run(
command,
- env={
- 'FUZZ': t
- },
+ env=get_fuzz_env(target=t, source_dir=src_dir),
check=True,
stderr=subprocess.PIPE,
universal_newlines=True,
@@ -209,13 +218,15 @@ def generate_corpus_seeds(*, fuzz_pool, build_dir, seed_dir, targets):
future.result()
-def merge_inputs(*, fuzz_pool, corpus, test_list, build_dir, merge_dir):
+def merge_inputs(*, fuzz_pool, corpus, test_list, src_dir, build_dir, merge_dir):
logging.info("Merge the inputs from the passed dir into the seed_dir. Passed dir {}".format(merge_dir))
jobs = []
for t in test_list:
args = [
os.path.join(build_dir, 'src', 'test', 'fuzz', 'fuzz'),
'-merge=1',
+ '-shuffle=0',
+ '-prefer_small=1',
'-use_value_profile=1', # Also done by oss-fuzz https://github.com/google/oss-fuzz/issues/1406#issuecomment-387790487
os.path.join(corpus, t),
os.path.join(merge_dir, t),
@@ -227,9 +238,7 @@ def merge_inputs(*, fuzz_pool, corpus, test_list, build_dir, merge_dir):
output = 'Run {} with args {}\n'.format(t, " ".join(args))
output += subprocess.run(
args,
- env={
- 'FUZZ': t
- },
+ env=get_fuzz_env(target=t, source_dir=src_dir),
check=True,
stderr=subprocess.PIPE,
universal_newlines=True,
@@ -242,7 +251,7 @@ def merge_inputs(*, fuzz_pool, corpus, test_list, build_dir, merge_dir):
future.result()
-def run_once(*, fuzz_pool, corpus, test_list, build_dir, use_valgrind):
+def run_once(*, fuzz_pool, corpus, test_list, src_dir, build_dir, use_valgrind):
jobs = []
for t in test_list:
corpus_path = os.path.join(corpus, t)
@@ -257,7 +266,12 @@ def run_once(*, fuzz_pool, corpus, test_list, build_dir, use_valgrind):
def job(t, args):
output = 'Run {} with args {}'.format(t, args)
- result = subprocess.run(args, env={'FUZZ': t}, stderr=subprocess.PIPE, universal_newlines=True)
+ result = subprocess.run(
+ args,
+ env=get_fuzz_env(target=t, source_dir=src_dir),
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
output += result.stderr
return output, result
diff --git a/test/lint/check-rpc-mappings.py b/test/lint/check-rpc-mappings.py
deleted file mode 100755
index 0a4cc875d0..0000000000
--- a/test/lint/check-rpc-mappings.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2017-2019 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""Check RPC argument consistency."""
-
-from collections import defaultdict
-import os
-import re
-import sys
-
-# Source files (relative to root) to scan for dispatch tables
-SOURCES = [
- "src/rpc/server.cpp",
- "src/rpc/blockchain.cpp",
- "src/rpc/mining.cpp",
- "src/rpc/misc.cpp",
- "src/rpc/net.cpp",
- "src/rpc/rawtransaction.cpp",
- "src/wallet/rpcwallet.cpp",
-]
-# Source file (relative to root) containing conversion mapping
-SOURCE_CLIENT = 'src/rpc/client.cpp'
-# Argument names that should be ignored in consistency checks
-IGNORE_DUMMY_ARGS = {'dummy', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6', 'arg7', 'arg8', 'arg9'}
-
-class RPCCommand:
- def __init__(self, name, args):
- self.name = name
- self.args = args
-
-class RPCArgument:
- def __init__(self, names, idx):
- self.names = names
- self.idx = idx
- self.convert = False
-
-def parse_string(s):
- assert s[0] == '"'
- assert s[-1] == '"'
- return s[1:-1]
-
-def process_commands(fname):
- """Find and parse dispatch table in implementation file `fname`."""
- cmds = []
- in_rpcs = False
- with open(fname, "r", encoding="utf8") as f:
- for line in f:
- line = line.rstrip()
- if not in_rpcs:
- if re.match(r"static const CRPCCommand .*\[\] =", line):
- in_rpcs = True
- else:
- if line.startswith('};'):
- in_rpcs = False
- elif '{' in line and '"' in line:
- m = re.search(r'{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line)
- assert m, 'No match to table expression: %s' % line
- name = parse_string(m.group(2))
- args_str = m.group(4).strip()
- if args_str:
- args = [RPCArgument(parse_string(x.strip()).split('|'), idx) for idx, x in enumerate(args_str.split(','))]
- else:
- args = []
- cmds.append(RPCCommand(name, args))
- assert not in_rpcs and cmds, "Something went wrong with parsing the C++ file: update the regexps"
- return cmds
-
-def process_mapping(fname):
- """Find and parse conversion table in implementation file `fname`."""
- cmds = []
- in_rpcs = False
- with open(fname, "r", encoding="utf8") as f:
- for line in f:
- line = line.rstrip()
- if not in_rpcs:
- if line == 'static const CRPCConvertParam vRPCConvertParams[] =':
- in_rpcs = True
- else:
- if line.startswith('};'):
- in_rpcs = False
- elif '{' in line and '"' in line:
- m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line)
- assert m, 'No match to table expression: %s' % line
- name = parse_string(m.group(1))
- idx = int(m.group(2))
- argname = parse_string(m.group(3))
- cmds.append((name, idx, argname))
- assert not in_rpcs and cmds
- return cmds
-
-def main():
- if len(sys.argv) != 2:
- print('Usage: {} ROOT-DIR'.format(sys.argv[0]), file=sys.stderr)
- sys.exit(1)
-
- root = sys.argv[1]
-
- # Get all commands from dispatch tables
- cmds = []
- for fname in SOURCES:
- cmds += process_commands(os.path.join(root, fname))
-
- cmds_by_name = {}
- for cmd in cmds:
- cmds_by_name[cmd.name] = cmd
-
- # Get current convert mapping for client
- client = SOURCE_CLIENT
- mapping = set(process_mapping(os.path.join(root, client)))
-
- print('* Checking consistency between dispatch tables and vRPCConvertParams')
-
- # Check mapping consistency
- errors = 0
- for (cmdname, argidx, argname) in mapping:
- try:
- rargnames = cmds_by_name[cmdname].args[argidx].names
- except IndexError:
- print('ERROR: %s argument %i (named %s in vRPCConvertParams) is not defined in dispatch table' % (cmdname, argidx, argname))
- errors += 1
- continue
- if argname not in rargnames:
- print('ERROR: %s argument %i is named %s in vRPCConvertParams but %s in dispatch table' % (cmdname, argidx, argname, rargnames), file=sys.stderr)
- errors += 1
-
- # Check for conflicts in vRPCConvertParams conversion
- # All aliases for an argument must either be present in the
- # conversion table, or not. Anything in between means an oversight
- # and some aliases won't work.
- for cmd in cmds:
- for arg in cmd.args:
- convert = [((cmd.name, arg.idx, argname) in mapping) for argname in arg.names]
- if any(convert) != all(convert):
- print('ERROR: %s argument %s has conflicts in vRPCConvertParams conversion specifier %s' % (cmd.name, arg.names, convert))
- errors += 1
- arg.convert = all(convert)
-
- # Check for conversion difference by argument name.
- # It is preferable for API consistency that arguments with the same name
- # have the same conversion, so bin by argument name.
- all_methods_by_argname = defaultdict(list)
- converts_by_argname = defaultdict(list)
- for cmd in cmds:
- for arg in cmd.args:
- for argname in arg.names:
- all_methods_by_argname[argname].append(cmd.name)
- converts_by_argname[argname].append(arg.convert)
-
- for argname, convert in converts_by_argname.items():
- if all(convert) != any(convert):
- if argname in IGNORE_DUMMY_ARGS:
- # these are testing or dummy, don't warn for them
- continue
- print('WARNING: conversion mismatch for argument named %s (%s)' %
- (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname]))))
-
- sys.exit(errors > 0)
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index c4ad00e954..5312dbbfdb 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -20,6 +20,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"txmempool -> validation -> txmempool"
"wallet/fees -> wallet/wallet -> wallet/fees"
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
+ "node/coinstats -> validation -> node/coinstats"
)
EXIT_CODE=0
diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh
index dc032665e4..bf7aeb5b4f 100755
--- a/test/lint/lint-includes.sh
+++ b/test/lint/lint-includes.sh
@@ -60,17 +60,11 @@ EXPECTED_BOOST_INCLUDES=(
boost/multi_index/ordered_index.hpp
boost/multi_index/sequenced_index.hpp
boost/multi_index_container.hpp
- boost/preprocessor/cat.hpp
- boost/preprocessor/stringize.hpp
boost/process.hpp
boost/signals2/connection.hpp
boost/signals2/optional_last_value.hpp
boost/signals2/signal.hpp
boost/test/unit_test.hpp
- boost/thread/condition_variable.hpp
- boost/thread/mutex.hpp
- boost/thread/shared_mutex.hpp
- boost/thread/thread.hpp
)
for BOOST_INCLUDE in $(git grep '^#include <boost/' -- "*.cpp" "*.h" | cut -f2 -d: | cut -f2 -d'<' | cut -f1 -d'>' | sort -u); do
diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh
new file mode 100755
index 0000000000..c3b6ff3c98
--- /dev/null
+++ b/test/lint/lint-python-dead-code.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 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.
+#
+# Find dead Python code.
+
+export LC_ALL=C
+
+if ! command -v vulture > /dev/null; then
+ echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\""
+ exit 0
+fi
+
+# --min-confidence 100 will only report code that is guaranteed to be unused within the analyzed files.
+# Any value below 100 introduces the risk of false positives, which would create an unacceptable maintenance burden.
+if ! vulture \
+ --min-confidence 100 \
+ $(git ls-files -- "*.py"); then
+ echo "Python dead code detection found some issues"
+ exit 1
+fi
diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan
index 3a04418e8b..3fc9fac25c 100644
--- a/test/sanitizer_suppressions/tsan
+++ b/test/sanitizer_suppressions/tsan
@@ -28,6 +28,7 @@ race:BerkeleyBatch
race:BerkeleyDatabase
race:DatabaseBatch
race:leveldb::DBImpl::DeleteObsoleteFiles
+race:validation_chainstatemanager_tests
race:zmq::*
race:bitcoin-qt
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index 9a52cd4b57..97f0f45e7f 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -1,3 +1,7 @@
+# -fsanitize=undefined suppressions
+# =================================
+# No suppressions at the moment. Hooray!
+
# -fsanitize=integer suppressions
# ===============================
# Unsigned integer overflow occurs when the result of an unsigned integer
@@ -6,7 +10,8 @@
# contains files in which we expect unsigned integer overflows to occur. The
# list is used to suppress -fsanitize=integer warnings when running our CI UBSan
# job.
-unsigned-integer-overflow:*/include/c++/*/bits/basic_string.tcc
+unsigned-integer-overflow:*/include/c++/
+unsigned-integer-overflow:addrman.cpp
unsigned-integer-overflow:arith_uint256.h
unsigned-integer-overflow:basic_string.h
unsigned-integer-overflow:bench/bench.h
@@ -15,34 +20,41 @@ unsigned-integer-overflow:bloom.cpp
unsigned-integer-overflow:chain.cpp
unsigned-integer-overflow:chain.h
unsigned-integer-overflow:coded_stream.h
+unsigned-integer-overflow:coins.cpp
+unsigned-integer-overflow:compressor.cpp
unsigned-integer-overflow:core_write.cpp
-unsigned-integer-overflow:crypto/*
+unsigned-integer-overflow:crypto/
+# unsigned-integer-overflow in FuzzedDataProvider's ConsumeIntegralInRange
+unsigned-integer-overflow:FuzzedDataProvider.h
unsigned-integer-overflow:hash.cpp
-unsigned-integer-overflow:leveldb/db/log_reader.cc
-unsigned-integer-overflow:leveldb/util/bloom.cc
-unsigned-integer-overflow:leveldb/util/crc32c.h
-unsigned-integer-overflow:leveldb/util/hash.cc
+unsigned-integer-overflow:leveldb/
unsigned-integer-overflow:policy/fees.cpp
unsigned-integer-overflow:prevector.h
+unsigned-integer-overflow:pubkey.h
unsigned-integer-overflow:script/interpreter.cpp
unsigned-integer-overflow:stl_bvector.h
unsigned-integer-overflow:txmempool.cpp
unsigned-integer-overflow:util/strencodings.cpp
unsigned-integer-overflow:validation.cpp
-
-implicit-integer-sign-change:*/include/c++/*/bits/*.h
+implicit-integer-sign-change:*/include/boost/
+implicit-integer-sign-change:*/include/c++/
implicit-integer-sign-change:*/new_allocator.h
-implicit-integer-sign-change:/usr/include/boost/date_time/format_date_parser.hpp
+implicit-integer-sign-change:addrman.h
implicit-integer-sign-change:arith_uint256.cpp
implicit-integer-sign-change:bech32.cpp
implicit-integer-sign-change:bloom.cpp
-implicit-integer-sign-change:chain.*
+implicit-integer-sign-change:chain.cpp
+implicit-integer-sign-change:chain.h
implicit-integer-sign-change:coins.h
implicit-integer-sign-change:compat/stdin.cpp
implicit-integer-sign-change:compressor.h
-implicit-integer-sign-change:crypto/*
+implicit-integer-sign-change:crc32c/
+implicit-integer-sign-change:crypto/
+# implicit-integer-sign-change in FuzzedDataProvider's ConsumeIntegralInRange
+implicit-integer-sign-change:FuzzedDataProvider.h
implicit-integer-sign-change:key.cpp
implicit-integer-sign-change:noui.cpp
+implicit-integer-sign-change:policy/fees.cpp
implicit-integer-sign-change:prevector.h
implicit-integer-sign-change:protocol.cpp
implicit-integer-sign-change:script/bitcoinconsensus.cpp
@@ -53,24 +65,38 @@ implicit-integer-sign-change:test/coins_tests.cpp
implicit-integer-sign-change:test/pow_tests.cpp
implicit-integer-sign-change:test/prevector_tests.cpp
implicit-integer-sign-change:test/sighash_tests.cpp
+implicit-integer-sign-change:test/skiplist_tests.cpp
implicit-integer-sign-change:test/streams_tests.cpp
implicit-integer-sign-change:test/transaction_tests.cpp
implicit-integer-sign-change:txmempool.cpp
-implicit-integer-sign-change:util/strencodings.*
+implicit-integer-sign-change:util/strencodings.cpp
+implicit-integer-sign-change:util/strencodings.h
implicit-integer-sign-change:validation.cpp
implicit-integer-sign-change:zmq/zmqpublishnotifier.cpp
implicit-signed-integer-truncation,implicit-integer-sign-change:chain.h
implicit-signed-integer-truncation,implicit-integer-sign-change:test/skiplist_tests.cpp
+implicit-signed-integer-truncation:addrman.cpp
+implicit-signed-integer-truncation:addrman.h
implicit-signed-integer-truncation:chain.h
-implicit-signed-integer-truncation:crypto/*
+implicit-signed-integer-truncation:crypto/
implicit-signed-integer-truncation:cuckoocache.h
-implicit-signed-integer-truncation:leveldb/*
+implicit-signed-integer-truncation:leveldb/
+implicit-signed-integer-truncation:net.cpp
+implicit-signed-integer-truncation:net_processing.cpp
implicit-signed-integer-truncation:streams.h
implicit-signed-integer-truncation:test/arith_uint256_tests.cpp
implicit-signed-integer-truncation:test/skiplist_tests.cpp
implicit-signed-integer-truncation:torcontrol.cpp
-implicit-unsigned-integer-truncation:crypto/*
-implicit-unsigned-integer-truncation:leveldb/*
+implicit-unsigned-integer-truncation:*/include/c++/
+implicit-unsigned-integer-truncation:crypto/
+implicit-unsigned-integer-truncation:leveldb/
# std::variant warning fixed in https://github.com/gcc-mirror/gcc/commit/074436cf8cdd2a9ce75cadd36deb8301f00e55b9
implicit-unsigned-integer-truncation:std::__detail::__variant::_Variant_storage
-implicit-integer-sign-change:crc32c/*
+shift-base:*/include/c++/
+shift-base:arith_uint256.cpp
+shift-base:crypto/
+shift-base:hash.cpp
+shift-base:leveldb/
+shift-base:net_processing.cpp
+shift-base:streams.h
+shift-base:util/bip32.cpp