aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md8
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py52
-rwxr-xr-xtest/functional/feature_bip68_sequence.py5
-rwxr-xr-xtest/functional/feature_config_args.py27
-rwxr-xr-xtest/functional/feature_loadblock.py3
-rwxr-xr-xtest/functional/feature_logging.py3
-rwxr-xr-xtest/functional/feature_maxuploadtarget.py17
-rwxr-xr-xtest/functional/feature_notifications.py1
-rwxr-xr-xtest/functional/feature_pruning.py12
-rwxr-xr-xtest/functional/feature_segwit.py3
-rwxr-xr-xtest/functional/feature_settings.py84
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py102
-rwxr-xr-xtest/functional/mempool_compatibility.py75
-rwxr-xr-xtest/functional/mempool_packages.py7
-rwxr-xr-xtest/functional/p2p_addr_relay.py4
-rwxr-xr-xtest/functional/p2p_blocksonly.py19
-rwxr-xr-xtest/functional/p2p_compactblocks.py7
-rwxr-xr-xtest/functional/p2p_disconnect_ban.py3
-rwxr-xr-xtest/functional/p2p_eviction.py129
-rwxr-xr-xtest/functional/p2p_feefilter.py65
-rwxr-xr-xtest/functional/p2p_filter.py6
-rwxr-xr-xtest/functional/p2p_ibd_txrelay.py42
-rwxr-xr-xtest/functional/p2p_invalid_messages.py261
-rwxr-xr-xtest/functional/p2p_leak.py26
-rwxr-xr-xtest/functional/p2p_nobloomfilter_messages.py23
-rwxr-xr-xtest/functional/p2p_node_network_limited.py1
-rwxr-xr-xtest/functional/p2p_permissions.py31
-rwxr-xr-xtest/functional/p2p_ping.py123
-rwxr-xr-xtest/functional/p2p_segwit.py106
-rwxr-xr-xtest/functional/p2p_tx_download.py12
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py12
-rwxr-xr-xtest/functional/rpc_blockchain.py11
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py14
-rwxr-xr-xtest/functional/rpc_getaddressinfo_label_deprecation.py43
-rwxr-xr-xtest/functional/rpc_getaddressinfo_labels_purpose_deprecation.py48
-rwxr-xr-xtest/functional/rpc_getblockfilter.py4
-rwxr-xr-xtest/functional/rpc_getpeerinfo_banscore_deprecation.py24
-rwxr-xr-xtest/functional/rpc_net.py10
-rwxr-xr-xtest/functional/rpc_psbt.py42
-rwxr-xr-xtest/functional/rpc_rawtransaction.py3
-rw-r--r--test/functional/test_framework/authproxy.py2
-rwxr-xr-xtest/functional/test_framework/messages.py43
-rwxr-xr-xtest/functional/test_framework/mininode.py28
-rwxr-xr-xtest/functional/test_framework/test_framework.py89
-rwxr-xr-xtest/functional/test_framework/test_node.py6
-rw-r--r--test/functional/test_framework/util.py108
-rwxr-xr-xtest/functional/test_runner.py15
-rwxr-xr-xtest/functional/tool_wallet.py3
-rwxr-xr-xtest/functional/wallet_abandonconflict.py9
-rwxr-xr-xtest/functional/wallet_avoidreuse.py4
-rwxr-xr-xtest/functional/wallet_balance.py3
-rwxr-xr-xtest/functional/wallet_basic.py129
-rwxr-xr-xtest/functional/wallet_bumpfee.py26
-rwxr-xr-xtest/functional/wallet_dump.py8
-rwxr-xr-xtest/functional/wallet_encryption.py16
-rwxr-xr-xtest/functional/wallet_fallbackfee.py2
-rwxr-xr-xtest/functional/wallet_hd.py6
-rwxr-xr-xtest/functional/wallet_importdescriptors.py12
-rwxr-xr-xtest/functional/wallet_multiwallet.py35
-rwxr-xr-xtest/functional/wallet_reorgsrestore.py3
-rwxr-xr-xtest/functional/wallet_upgradewallet.py6
-rwxr-xr-xtest/functional/wallet_zapwallettxes.py15
-rw-r--r--test/lint/README.md6
-rwxr-xr-xtest/lint/git-subtree-check.sh2
-rwxr-xr-xtest/lint/lint-assertions.sh2
-rwxr-xr-xtest/lint/lint-includes.sh2
-rwxr-xr-xtest/lint/lint-locale-dependence.sh1
-rwxr-xr-xtest/lint/lint-python.sh1
-rwxr-xr-xtest/lint/lint-shell.sh1
-rw-r--r--test/lint/lint-spelling.ignore-words.txt1
-rw-r--r--test/sanitizer_suppressions/tsan3
71 files changed, 1481 insertions, 574 deletions
diff --git a/test/README.md b/test/README.md
index b036a66f67..2341eef00d 100644
--- a/test/README.md
+++ b/test/README.md
@@ -260,11 +260,11 @@ Use the `-v` option for verbose output.
| Lint test | Dependency | Version [used by CI](../ci/lint/04_install.sh) | Installation
|-----------|:----------:|:-------------------------------------------:|--------------
-| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.7.8](https://github.com/bitcoin/bitcoin/pull/15257) | `pip3 install flake8==3.7.8`
-| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.700](https://github.com/bitcoin/bitcoin/pull/18210) | `pip3 install mypy==0.700`
-| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.6.0](https://github.com/bitcoin/bitcoin/pull/15166) | [details...](https://github.com/koalaman/shellcheck#installing)
+| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.8.3](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install flake8==3.8.3`
+| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.781](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install mypy==0.781`
+| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.7.1](https://github.com/bitcoin/bitcoin/pull/19348) | [details...](https://github.com/koalaman/shellcheck#installing)
| [`lint-shell.sh`](lint/lint-shell.sh) | [yq](https://github.com/kislyuk/yq) | default | `pip3 install yq`
-| [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [1.15.0](https://github.com/bitcoin/bitcoin/pull/16186) | `pip3 install codespell==1.15.0`
+| [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [1.17.1](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install codespell==1.17.1`
Please be aware that on Linux distributions all dependencies are usually available as packages, but could be outdated.
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index 596ff206f2..07dd0f8f82 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -6,7 +6,7 @@
Test various backwards compatibility scenarios. Download the previous node binaries:
-contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+contrib/devtools/previous_release.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py.
Due to a hardfork in regtest, it can't be used to sync nodes.
@@ -26,10 +26,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.descriptors import descsum_create
from test_framework.util import (
- adjust_bitcoin_conf_for_pre_17,
assert_equal,
- sync_blocks,
- sync_mempools,
)
@@ -60,15 +57,13 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
170100,
160300,
])
- # adapt bitcoin.conf, because older bitcoind's don't recognize config sections
- adjust_bitcoin_conf_for_pre_17(self.nodes[5].bitcoinconf)
self.start_nodes()
def run_test(self):
self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress())
- sync_blocks(self.nodes)
+ self.sync_blocks()
# Sanity check the test framework:
res = self.nodes[self.num_nodes - 1].getblockchaininfo()
@@ -93,17 +88,17 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# Create a confirmed transaction, receiving coins
address = wallet.getnewaddress()
self.nodes[0].sendtoaddress(address, 10)
- sync_mempools(self.nodes)
+ self.sync_mempools()
self.nodes[0].generate(1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
# Create a conflicting transaction using RBF
return_address = self.nodes[0].getnewaddress()
tx1_id = self.nodes[1].sendtoaddress(return_address, 1)
tx2_id = self.nodes[1].bumpfee(tx1_id)["txid"]
# Confirm the transaction
- sync_mempools(self.nodes)
+ self.sync_mempools()
self.nodes[0].generate(1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
# Create another conflicting transaction using RBF
tx3_id = self.nodes[1].sendtoaddress(return_address, 1)
tx4_id = self.nodes[1].bumpfee(tx3_id)["txid"]
@@ -316,12 +311,19 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
self.start_node(4)
# Open most recent wallet in v0.16 (no loadwallet RPC)
- self.stop_node(5)
- self.start_node(5, extra_args=["-wallet=w2"])
+ self.restart_node(5, extra_args=["-wallet=w2"])
wallet = node_v16.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['keypoolsize'] == 1
+ # Create upgrade wallet in v0.16
+ self.restart_node(-1, extra_args=["-wallet=u1_v16"])
+ wallet = node_v16.get_wallet_rpc("u1_v16")
+ v16_addr = wallet.getnewaddress('', "bech32")
+ v16_info = wallet.validateaddress(v16_addr)
+ v16_pubkey = v16_info['pubkey']
+ self.stop_node(-1)
+
self.log.info("Test wallet upgrade path...")
# u1: regular wallet, created with v0.17
node_v17.rpc.createwallet(wallet_name="u1_v17")
@@ -331,6 +333,30 @@ 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(
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index 549e8b2029..19cdc10935 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -30,7 +30,10 @@ class BIP68Test(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.extra_args = [
- ["-acceptnonstdtxn=1"],
+ [
+ "-acceptnonstdtxn=1",
+ "-peertimeout=9999", # bump because mocktime might cause a disconnect otherwise
+ ],
["-acceptnonstdtxn=0"],
]
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index a4dc455d57..34e856c1ba 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -112,11 +112,38 @@ class ConfArgsTest(BitcoinTestFramework):
])
self.stop_node(0)
+ def test_networkactive(self):
+ self.log.info('Test -networkactive option')
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.start_node(0, extra_args=['-networkactive'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.start_node(0, extra_args=['-networkactive=1'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.start_node(0, extra_args=['-networkactive=0'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.start_node(0, extra_args=['-nonetworkactive'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.start_node(0, extra_args=['-nonetworkactive=1'])
+ self.stop_node(0)
+
def run_test(self):
self.stop_node(0)
self.test_log_buffer()
self.test_args_log()
+ self.test_networkactive()
self.test_config_file_parser()
diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py
index 82f1331685..0a457ca17f 100755
--- a/test/functional/feature_loadblock.py
+++ b/test/functional/feature_loadblock.py
@@ -71,8 +71,7 @@ class LoadblockTest(BitcoinTestFramework):
check=True)
self.log.info("Restart second, unsynced node with bootstrap file")
- self.stop_node(1)
- self.start_node(1, ["-loadblock=" + bootstrap_file])
+ self.restart_node(1, extra_args=["-loadblock=" + bootstrap_file])
assert_equal(self.nodes[1].getblockcount(), 100) # start_node is blocking on all block files being imported
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 100)
diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py
index e4bf2d849d..afcbcf099a 100755
--- a/test/functional/feature_logging.py
+++ b/test/functional/feature_logging.py
@@ -67,8 +67,7 @@ class LoggingTest(BitcoinTestFramework):
assert not os.path.isfile(default_log_path)
# just sanity check no crash here
- self.stop_node(0)
- self.start_node(0, ["-debuglogfile=%s" % os.devnull])
+ self.restart_node(0, ["-debuglogfile=%s" % os.devnull])
if __name__ == '__main__':
diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py
index 7eabf86cad..5538d6d3b4 100755
--- a/test/functional/feature_maxuploadtarget.py
+++ b/test/functional/feature_maxuploadtarget.py
@@ -35,7 +35,11 @@ class MaxUploadTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
- self.extra_args = [["-maxuploadtarget=800", "-acceptnonstdtxn=1"]]
+ self.extra_args = [[
+ "-maxuploadtarget=800",
+ "-acceptnonstdtxn=1",
+ "-peertimeout=9999", # bump because mocktime might cause a disconnect otherwise
+ ]]
self.supports_cli = False
# Cache for utxos, as the listunspent may take a long time later in the test
@@ -137,8 +141,8 @@ class MaxUploadTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
- self.log.info("Restarting node 0 with noban permission and 1MB maxuploadtarget")
- self.restart_node(0, ["-whitelist=noban@127.0.0.1", "-maxuploadtarget=1"])
+ self.log.info("Restarting node 0 with download permission and 1MB maxuploadtarget")
+ self.restart_node(0, ["-whitelist=download@127.0.0.1", "-maxuploadtarget=1"])
# Reconnect to self.nodes[0]
self.nodes[0].add_p2p_connection(TestP2PConn())
@@ -151,9 +155,12 @@ class MaxUploadTest(BitcoinTestFramework):
getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]
self.nodes[0].p2p.send_and_ping(getdata_request)
- assert_equal(len(self.nodes[0].getpeerinfo()), 1) #node is still connected because of the noban permission
- self.log.info("Peer still connected after trying to download old block (noban permission)")
+ self.log.info("Peer still connected after trying to download old block (download permission)")
+ peer_info = self.nodes[0].getpeerinfo()
+ assert_equal(len(peer_info), 1) # node is still connected
+ assert_equal(peer_info[0]['permissions'], ['download'])
+
if __name__ == '__main__':
MaxUploadTest().main()
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index 33a308ad1b..dd4c318cee 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -93,6 +93,7 @@ class NotificationsTest(BitcoinTestFramework):
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()
# Generate transaction on node 0, sync mempools, and check for
# notification on node 1.
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index c9362cf5aa..e46e5aacc8 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -263,8 +263,7 @@ class PruneTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "not in prune mode", node.pruneblockchain, 500)
# now re-start in manual pruning mode
- self.stop_node(node_number)
- self.start_node(node_number, extra_args=["-prune=1"])
+ self.restart_node(node_number, extra_args=["-prune=1"])
node = self.nodes[node_number]
assert_equal(node.getblockcount(), 995)
@@ -326,16 +325,14 @@ class PruneTest(BitcoinTestFramework):
assert not has_block(3), "blk00003.dat is still there, should be pruned by now"
# stop node, start back up with auto-prune at 550 MiB, make sure still runs
- self.stop_node(node_number)
- self.start_node(node_number, extra_args=["-prune=550"])
+ self.restart_node(node_number, extra_args=["-prune=550"])
self.log.info("Success")
def wallet_test(self):
# check that the pruning node's wallet is still in good shape
self.log.info("Stop and start pruning node to trigger wallet rescan")
- self.stop_node(2)
- self.start_node(2, extra_args=["-prune=550"])
+ self.restart_node(2, extra_args=["-prune=550"])
self.log.info("Success")
# check that wallet loads successfully when restarting a pruned node after IBD.
@@ -344,8 +341,7 @@ class PruneTest(BitcoinTestFramework):
connect_nodes(self.nodes[0], 5)
nds = [self.nodes[0], self.nodes[5]]
self.sync_blocks(nds, wait=5, timeout=300)
- self.stop_node(5) # stop and start to trigger rescan
- self.start_node(5, extra_args=["-prune=550"])
+ self.restart_node(5, extra_args=["-prune=550"]) # restart to trigger rescan
self.log.info("Success")
def run_test(self):
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 2298485640..5195d20dcb 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -559,8 +559,7 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
# Assert it is properly saved
- self.stop_node(1)
- self.start_node(1)
+ self.restart_node(1)
assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid)
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
new file mode 100755
index 0000000000..c565854bb0
--- /dev/null
+++ b/test/functional/feature_settings.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017-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 various command line arguments and configuration file parameters."""
+
+import json
+
+from pathlib import Path
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.test_node import ErrorMatch
+from test_framework.util import assert_equal
+
+
+class SettingsTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def run_test(self):
+ node, = self.nodes
+ settings = Path(node.datadir, self.chain, "settings.json")
+ conf = Path(node.datadir, "bitcoin.conf")
+
+ # Assert empty settings file was created
+ self.stop_node(0)
+ with settings.open() as fp:
+ assert_equal(json.load(fp), {})
+
+ # Assert settings are parsed and logged
+ with settings.open("w") as fp:
+ json.dump({"string": "string", "num": 5, "bool": True, "null": None, "list": [6,7]}, fp)
+ with node.assert_debug_log(expected_msgs=[
+ 'Setting file arg: string = "string"',
+ 'Setting file arg: num = 5',
+ 'Setting file arg: bool = true',
+ 'Setting file arg: null = null',
+ 'Setting file arg: list = [6,7]']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ # Assert settings are unchanged after shutdown
+ with settings.open() as fp:
+ assert_equal(json.load(fp), {"string": "string", "num": 5, "bool": True, "null": None, "list": [6,7]})
+
+ # Test invalid json
+ with settings.open("w") as fp:
+ fp.write("invalid json")
+ node.assert_start_raises_init_error(expected_msg='Unable to parse settings file', match=ErrorMatch.PARTIAL_REGEX)
+
+ # Test invalid json object
+ with settings.open("w") as fp:
+ fp.write('"string"')
+ node.assert_start_raises_init_error(expected_msg='Found non-object value "string" in settings file', match=ErrorMatch.PARTIAL_REGEX)
+
+ # Test invalid settings file containing duplicate keys
+ with settings.open("w") as fp:
+ fp.write('{"key": 1, "key": 2}')
+ node.assert_start_raises_init_error(expected_msg='Found duplicate key key in settings file', match=ErrorMatch.PARTIAL_REGEX)
+
+ # Test invalid settings file is ignored with command line -nosettings
+ with node.assert_debug_log(expected_msgs=['Command-line arg: settings=false']):
+ self.start_node(0, extra_args=["-nosettings"])
+ self.stop_node(0)
+
+ # Test invalid settings file is ignored with config file -nosettings
+ with conf.open('a') as conf:
+ conf.write('nosettings=1\n')
+ with node.assert_debug_log(expected_msgs=['Config file arg: [regtest] settings=false']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ # Test alternate settings path
+ altsettings = Path(node.datadir, "altsettings.json")
+ with altsettings.open("w") as fp:
+ fp.write('{"key": "value"}')
+ with node.assert_debug_log(expected_msgs=['Setting file arg: key = "value"']):
+ self.start_node(0, extra_args=["-settings={}".format(altsettings)])
+ self.stop_node(0)
+
+
+if __name__ == '__main__':
+ SettingsTest().main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 7530e7daf6..80003aca0d 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -3,9 +3,15 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test bitcoin-cli"""
+
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_process_error, get_auth_cookie
+from test_framework.util import (
+ assert_equal,
+ assert_raises_process_error,
+ assert_raises_rpc_error,
+ get_auth_cookie,
+)
# The block reward of coinbaseoutput.nValue (50) BTC/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect
@@ -13,6 +19,12 @@ from test_framework.util import assert_equal, assert_raises_process_error, get_a
BLOCKS = 101
BALANCE = (BLOCKS - 100) * 50
+JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
+BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
+TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'
+WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'
+WALLET_NOT_SPECIFIED = 'Wallet file not specified'
+
class TestBitcoinCli(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -75,7 +87,7 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
- # Setup to test -getinfo and -rpcwallet= with multiple wallets.
+ # Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
wallets = ['', 'Encrypted', 'secret']
amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)]
self.nodes[0].createwallet(wallet_name=wallets[1])
@@ -83,6 +95,8 @@ class TestBitcoinCli(BitcoinTestFramework):
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
+ rpcwallet2 = '-rpcwallet={}'.format(wallets[1])
+ rpcwallet3 = '-rpcwallet={}'.format(wallets[2])
w1.walletpassphrase(password, self.rpc_timeout)
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
@@ -123,17 +137,93 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(cli_get_info['balance'], amounts[1])
self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance")
- cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[1])).send_cli()
+ cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
assert 'balances' not in cli_get_info.keys()
assert_equal(cli_get_info['balance'], amounts[1])
self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances")
- cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[2])).send_cli()
+ cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli()
assert 'balance' not in cli_get_info_keys
assert 'balances' not in cli_get_info_keys
+
+ # Test bitcoin-cli -generate.
+ n1 = 3
+ n2 = 4
+ w2.walletpassphrase(password, self.rpc_timeout)
+ blocks = self.nodes[0].getblockcount()
+
+ self.log.info('Test -generate with no args')
+ generate = self.nodes[0].cli('-generate').send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), 1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1)
+
+ self.log.info('Test -generate with bad args')
+ assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli('-generate', 'foo').echo)
+ assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli('-generate', 0).echo)
+ assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli('-generate', 1, 2, 3).echo)
+
+ self.log.info('Test -generate with nblocks')
+ generate = self.nodes[0].cli('-generate', n1).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1)
+
+ self.log.info('Test -generate with nblocks and maxtries')
+ generate = self.nodes[0].cli('-generate', n2, 1000000).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n2)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1 + n2)
+
+ self.log.info('Test -generate -rpcwallet in single-wallet mode')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), 1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 2 + n1 + n2)
+
+ self.log.info('Test -generate -rpcwallet=unloaded wallet raises RPC error')
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate').echo)
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 'foo').echo)
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 0).echo)
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 1, 2, 3).echo)
+
+ # Test bitcoin-cli -generate with -rpcwallet in multiwallet mode.
+ self.nodes[0].loadwallet(wallets[2])
+ n3 = 4
+ n4 = 10
+ blocks = self.nodes[0].getblockcount()
+
+ self.log.info('Test -generate -rpcwallet with no args')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), 1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1)
+
+ self.log.info('Test -generate -rpcwallet with bad args')
+ assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli(rpcwallet2, '-generate', 'foo').echo)
+ assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli(rpcwallet2, '-generate', 0).echo)
+ assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli(rpcwallet2, '-generate', 1, 2, 3).echo)
+
+ self.log.info('Test -generate -rpcwallet with nblocks')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate', n3).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n3)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3)
+
+ self.log.info('Test -generate -rpcwallet with nblocks and maxtries')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate', n4, 1000000).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n4)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3 + n4)
+
+ self.log.info('Test -generate without -rpcwallet in multiwallet mode raises RPC error')
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate').echo)
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 'foo').echo)
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 0).echo)
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 1, 2, 3).echo)
else:
self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
- self.nodes[0].generate(1) # maintain block parity with the wallet_compiled conditional branch
+ self.nodes[0].generate(25) # maintain block parity with the wallet_compiled conditional branch
self.log.info("Test -version with node stopped")
self.stop_node(0)
@@ -145,7 +235,7 @@ class TestBitcoinCli(BitcoinTestFramework):
self.nodes[0].wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition
blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount')
self.nodes[0].wait_for_rpc_connection()
- assert_equal(blocks, BLOCKS + 1)
+ assert_equal(blocks, BLOCKS + 25)
if __name__ == '__main__':
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
new file mode 100755
index 0000000000..31fb751904
--- /dev/null
+++ b/test/functional/mempool_compatibility.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017-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 that mempool.dat is both backward and forward compatible between versions
+
+NOTE: The test is designed to prevent cases when compatibility is broken accidentally.
+In case we need to break mempool compatibility we can continue to use the test by just bumping the version number.
+
+Download node binaries:
+contrib/devtools/previous_release.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+
+Only v0.15.2 is required by this test. The rest is used in other backwards compatibility tests.
+"""
+
+import os
+
+from test_framework.test_framework import BitcoinTestFramework
+
+
+class MempoolCompatibilityTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+ self.skip_if_no_previous_releases()
+
+ def setup_network(self):
+ self.add_nodes(self.num_nodes, versions=[
+ 150200, # oldest version supported by the test framework
+ None,
+ ])
+ self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
+
+ def run_test(self):
+ self.log.info("Test that mempool.dat is compatible between versions")
+
+ old_node = self.nodes[0]
+ new_node = self.nodes[1]
+ recipient = old_node.getnewaddress()
+ self.stop_node(1)
+
+ self.log.info("Add a transaction to mempool on old node and shutdown")
+ old_tx_hash = old_node.sendtoaddress(recipient, 0.0001)
+ assert old_tx_hash in old_node.getrawmempool()
+ self.stop_node(0)
+
+ self.log.info("Move mempool.dat from old to new node")
+ old_node_mempool = os.path.join(old_node.datadir, self.chain, 'mempool.dat')
+ new_node_mempool = os.path.join(new_node.datadir, self.chain, 'mempool.dat')
+ os.rename(old_node_mempool, new_node_mempool)
+
+ self.log.info("Start new node and verify mempool contains the tx")
+ self.start_node(1)
+ assert old_tx_hash in new_node.getrawmempool()
+
+ self.log.info("Add unbroadcasted tx to mempool on new node and shutdown")
+ unbroadcasted_tx_hash = new_node.sendtoaddress(recipient, 0.0001)
+ assert unbroadcasted_tx_hash in new_node.getrawmempool()
+ mempool = new_node.getrawmempool(True)
+ assert mempool[unbroadcasted_tx_hash]['unbroadcast']
+ self.stop_node(1)
+
+ self.log.info("Move mempool.dat from new to old node")
+ os.rename(new_node_mempool, old_node_mempool)
+
+ self.log.info("Start old node again and verify mempool contains both txs")
+ self.start_node(0, ['-nowallet'])
+ assert old_tx_hash in old_node.getrawmempool()
+ assert unbroadcasted_tx_hash in old_node.getrawmempool()
+
+if __name__ == "__main__":
+ MempoolCompatibilityTest().main()
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index 5b7216b253..542d24f4be 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -69,14 +69,19 @@ class MempoolPackagesTest(BitcoinTestFramework):
fee = Decimal("0.0001")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
+ witness_chain = []
for i in range(MAX_ANCESTORS):
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1)
value = sent_value
chain.append(txid)
+ # We need the wtxids to check P2P announcements
+ fulltx = self.nodes[0].getrawtransaction(txid)
+ witnesstx = self.nodes[0].decoderawtransaction(fulltx, True)
+ witness_chain.append(witnesstx['hash'])
# Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)
# Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
- self.nodes[0].p2p.wait_for_broadcast(chain)
+ self.nodes[0].p2p.wait_for_broadcast(witness_chain)
# Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor
# count and fees should look correct
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index 6046237101..5c7e27a3a8 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -49,9 +49,9 @@ class AddrTest(BitcoinTestFramework):
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = msg_addr()
- self.log.info('Send too large addr message')
+ self.log.info('Send too-large addr message')
msg.addrs = ADDRS * 101
- with self.nodes[0].assert_debug_log(['message addr size() = 1010']):
+ with self.nodes[0].assert_debug_log(['addr message size = 1010']):
addr_source.send_and_ping(msg)
self.log.info('Check that addr message content is relayed and added to addrman')
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index c155dda664..27e6b669f6 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -52,34 +52,35 @@ class P2PBlocksOnly(BitcoinTestFramework):
self.log.info('Check that txs from rpc are not rejected and relayed to other peers')
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True)
txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid']
- with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=1'.format(txid)]):
+ with self.nodes[0].assert_debug_log(['received getdata for: wtx {} peer=1'.format(txid)]):
self.nodes[0].sendrawtransaction(sigtx)
self.nodes[0].p2p.wait_for_tx(txid)
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
- self.log.info('Check that txs from whitelisted peers are not rejected and relayed to others')
- self.log.info("Restarting node 0 with whitelist permission and blocksonly")
+ self.log.info('Check that txs from forcerelay peers are not rejected and relayed to others')
+ self.log.info("Restarting node 0 with forcerelay permission and blocksonly")
self.restart_node(0, ["-persistmempool=0", "-whitelist=127.0.0.1", "-whitelistforcerelay", "-blocksonly"])
- assert_equal(self.nodes[0].getrawmempool(),[])
+ assert_equal(self.nodes[0].getrawmempool(), [])
first_peer = self.nodes[0].add_p2p_connection(P2PInterface())
second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
peer_1_info = self.nodes[0].getpeerinfo()[0]
assert_equal(peer_1_info['whitelisted'], True)
- assert_equal(peer_1_info['permissions'], ['noban', 'forcerelay', 'relay', 'mempool'])
+ assert_equal(peer_1_info['permissions'], ['noban', 'forcerelay', 'relay', 'mempool', 'download'])
peer_2_info = self.nodes[0].getpeerinfo()[1]
assert_equal(peer_2_info['whitelisted'], True)
- assert_equal(peer_2_info['permissions'], ['noban', 'forcerelay', 'relay', 'mempool'])
+ assert_equal(peer_2_info['permissions'], ['noban', 'forcerelay', 'relay', 'mempool', 'download'])
assert_equal(self.nodes[0].testmempoolaccept([sigtx])[0]['allowed'], True)
txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid']
- self.log.info('Check that the tx from whitelisted first_peer is relayed to others (ie.second_peer)')
+ self.log.info('Check that the tx from forcerelay first_peer is relayed to others (ie.second_peer)')
with self.nodes[0].assert_debug_log(["received getdata"]):
first_peer.send_message(msg_tx(FromHex(CTransaction(), sigtx)))
- self.log.info('Check that the whitelisted peer is still connected after sending the transaction')
+ self.log.info('Check that the forcerelay peer is still connected after sending the transaction')
assert_equal(first_peer.is_connected, True)
second_peer.wait_for_tx(txid)
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
- self.log.info("Whitelisted peer's transaction is accepted and relayed")
+ self.log.info("Forcerelay peer's transaction is accepted and relayed")
+
if __name__ == '__main__':
P2PBlocksOnly().main()
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index d77a744758..0b3738b572 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -305,10 +305,9 @@ class CompactBlocksTest(BitcoinTestFramework):
self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
# Now fetch the compact block using a normal non-announce getdata
- with mininode_lock:
- test_node.clear_block_announcement()
- inv = CInv(MSG_CMPCT_BLOCK, block_hash)
- test_node.send_message(msg_getdata([inv]))
+ test_node.clear_block_announcement()
+ inv = CInv(MSG_CMPCT_BLOCK, block_hash)
+ test_node.send_message(msg_getdata([inv]))
wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock)
diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py
index 9047fc6828..09b9ebeb2d 100755
--- a/test/functional/p2p_disconnect_ban.py
+++ b/test/functional/p2p_disconnect_ban.py
@@ -69,8 +69,7 @@ class DisconnectBanTest(BitcoinTestFramework):
self.nodes[1].setmocktime(old_time + 3)
assert_equal(len(self.nodes[1].listbanned()), 3)
- self.stop_node(1)
- self.start_node(1)
+ self.restart_node(1)
listAfterShutdown = self.nodes[1].listbanned()
assert_equal("127.0.0.0/24", listAfterShutdown[0]['address'])
diff --git a/test/functional/p2p_eviction.py b/test/functional/p2p_eviction.py
new file mode 100755
index 0000000000..b2b3a89aab
--- /dev/null
+++ b/test/functional/p2p_eviction.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+# Copyright (c) 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.
+
+""" Test node eviction logic
+
+When the number of peers has reached the limit of maximum connections,
+the next connecting inbound peer will trigger the eviction mechanism.
+We cannot currently test the parts of the eviction logic that are based on
+address/netgroup since in the current framework, all peers are connecting from
+the same local address. See Issue #14210 for more info.
+Therefore, this test is limited to the remaining protection criteria.
+"""
+
+import time
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.mininode import P2PInterface, P2PDataStore
+from test_framework.util import assert_equal, wait_until
+from test_framework.blocktools import create_block, create_coinbase
+from test_framework.messages import CTransaction, FromHex, msg_pong, msg_tx
+
+
+class SlowP2PDataStore(P2PDataStore):
+ def on_ping(self, message):
+ time.sleep(0.1)
+ self.send_message(msg_pong(message.nonce))
+
+class SlowP2PInterface(P2PInterface):
+ def on_ping(self, message):
+ time.sleep(0.1)
+ self.send_message(msg_pong(message.nonce))
+
+class P2PEvict(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ # The choice of maxconnections=32 results in a maximum of 21 inbound connections
+ # (32 - 10 outbound - 1 feeler). 20 inbound peers are protected from eviction:
+ # 4 by netgroup, 4 that sent us blocks, 4 that sent us transactions and 8 via lowest ping time
+ self.extra_args = [['-maxconnections=32']]
+
+ def run_test(self):
+ protected_peers = set() # peers that we expect to be protected from eviction
+ current_peer = -1
+ node = self.nodes[0]
+ node.generatetoaddress(101, node.get_deterministic_priv_key().address)
+
+ self.log.info("Create 4 peers and protect them from eviction by sending us a block")
+ for _ in range(4):
+ block_peer = node.add_p2p_connection(SlowP2PDataStore())
+ current_peer += 1
+ block_peer.sync_with_ping()
+ best_block = node.getbestblockhash()
+ tip = int(best_block, 16)
+ best_block_time = node.getblock(best_block)['time']
+ block = create_block(tip, create_coinbase(node.getblockcount() + 1), best_block_time + 1)
+ block.solve()
+ block_peer.send_blocks_and_test([block], node, success=True)
+ protected_peers.add(current_peer)
+
+ self.log.info("Create 5 slow-pinging peers, making them eviction candidates")
+ for _ in range(5):
+ node.add_p2p_connection(SlowP2PInterface())
+ current_peer += 1
+
+ self.log.info("Create 4 peers and protect them from eviction by sending us a tx")
+ for i in range(4):
+ txpeer = node.add_p2p_connection(SlowP2PInterface())
+ current_peer += 1
+ txpeer.sync_with_ping()
+
+ prevtx = node.getblock(node.getblockhash(i + 1), 2)['tx'][0]
+ rawtx = node.createrawtransaction(
+ inputs=[{'txid': prevtx['txid'], 'vout': 0}],
+ outputs=[{node.get_deterministic_priv_key().address: 50 - 0.00125}],
+ )
+ sigtx = node.signrawtransactionwithkey(
+ hexstring=rawtx,
+ privkeys=[node.get_deterministic_priv_key().key],
+ prevtxs=[{
+ 'txid': prevtx['txid'],
+ 'vout': 0,
+ 'scriptPubKey': prevtx['vout'][0]['scriptPubKey']['hex'],
+ }],
+ )['hex']
+ txpeer.send_message(msg_tx(FromHex(CTransaction(), sigtx)))
+ protected_peers.add(current_peer)
+
+ self.log.info("Create 8 peers and protect them from eviction by having faster pings")
+ for _ in range(8):
+ fastpeer = node.add_p2p_connection(P2PInterface())
+ current_peer += 1
+ wait_until(lambda: "ping" in fastpeer.last_message, timeout=10)
+
+ # Make sure by asking the node what the actual min pings are
+ peerinfo = node.getpeerinfo()
+ pings = {}
+ for i in range(len(peerinfo)):
+ pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[i] else 1000000
+ sorted_pings = sorted(pings.items(), key=lambda x: x[1])
+
+ # Usually the 8 fast peers are protected. In rare case of unreliable pings,
+ # one of the slower peers might have a faster min ping though.
+ for i in range(8):
+ protected_peers.add(sorted_pings[i][0])
+
+ self.log.info("Create peer that triggers the eviction mechanism")
+ node.add_p2p_connection(SlowP2PInterface())
+
+ # One of the non-protected peers must be evicted. We can't be sure which one because
+ # 4 peers are protected via netgroup, which is identical for all peers,
+ # and the eviction mechanism doesn't preserve the order of identical elements.
+ evicted_peers = []
+ for i in range(len(node.p2ps)):
+ if not node.p2ps[i].is_connected:
+ evicted_peers.append(i)
+
+ self.log.info("Test that one peer was evicted")
+ self.log.debug("{} evicted peer: {}".format(len(evicted_peers), set(evicted_peers)))
+ assert_equal(len(evicted_peers), 1)
+
+ self.log.info("Test that no peer expected to be protected was evicted")
+ self.log.debug("{} protected peers: {}".format(len(protected_peers), protected_peers))
+ assert evicted_peers[0] not in protected_peers
+
+if __name__ == '__main__':
+ P2PEvict().main()
diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py
index 805cb1e84f..73afe9adc4 100755
--- a/test/functional/p2p_feefilter.py
+++ b/test/functional/p2p_feefilter.py
@@ -7,14 +7,16 @@
from decimal import Decimal
import time
-from test_framework.messages import MSG_TX, msg_feefilter
+from test_framework.messages import MSG_TX, MSG_WTX, msg_feefilter
from test_framework.mininode import mininode_lock, P2PInterface
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
def hashToHex(hash):
return format(hash, '064x')
+
# Wait up to 60 secs to see if the testnode has received all the expected invs
def allInvsMatch(invsExpected, testnode):
for x in range(60):
@@ -24,6 +26,18 @@ def allInvsMatch(invsExpected, testnode):
time.sleep(1)
return False
+
+class FeefilterConn(P2PInterface):
+ feefilter_received = False
+
+ def on_feefilter(self, message):
+ self.feefilter_received = True
+
+ def assert_feefilter_received(self, recv: bool):
+ with mininode_lock:
+ assert_equal(self.feefilter_received, recv)
+
+
class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()
@@ -31,13 +45,14 @@ class TestP2PConn(P2PInterface):
def on_inv(self, message):
for i in message.inv:
- if (i.type == MSG_TX):
+ if (i.type == MSG_TX) or (i.type == MSG_WTX):
self.txinvs.append(hashToHex(i.hash))
def clear_invs(self):
with mininode_lock:
self.txinvs = []
+
class FeeFilterTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
@@ -46,41 +61,54 @@ class FeeFilterTest(BitcoinTestFramework):
# mempool and wallet feerate calculation based on GetFee
# rounding down 3 places, leading to stranded transactions.
# See issue #16499
- self.extra_args = [["-minrelaytxfee=0.00000100", "-mintxfee=0.00000100"]]*self.num_nodes
+ self.extra_args = [["-minrelaytxfee=0.00000100", "-mintxfee=0.00000100"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
+ self.test_feefilter_forcerelay()
+ self.test_feefilter()
+
+ def test_feefilter_forcerelay(self):
+ self.log.info('Check that peers without forcerelay permission (default) get a feefilter message')
+ self.nodes[0].add_p2p_connection(FeefilterConn()).assert_feefilter_received(True)
+
+ self.log.info('Check that peers with forcerelay permission do not get a feefilter message')
+ self.restart_node(0, extra_args=['-whitelist=forcerelay@127.0.0.1'])
+ self.nodes[0].add_p2p_connection(FeefilterConn()).assert_feefilter_received(False)
+
+ # Restart to disconnect peers and load default extra_args
+ self.restart_node(0)
+ self.connect_nodes(1, 0)
+
+ def test_feefilter(self):
node1 = self.nodes[1]
node0 = self.nodes[0]
- # Get out of IBD
- node1.generate(1)
- self.sync_blocks()
- self.nodes[0].add_p2p_connection(TestP2PConn())
+ conn = self.nodes[0].add_p2p_connection(TestP2PConn())
# Test that invs are received by test connection for all txs at
# feerate of .2 sat/byte
node1.settxfee(Decimal("0.00000200"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
# Set a filter of .15 sat/byte on test connection
- self.nodes[0].p2p.send_and_ping(msg_feefilter(150))
+ conn.send_and_ping(msg_feefilter(150))
# Test that txs are still being received by test connection (paying .15 sat/byte)
node1.settxfee(Decimal("0.00000150"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
# Change tx fee rate to .1 sat/byte and test they are no longer received
# by the test connection
node1.settxfee(Decimal("0.00000100"))
[node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- self.sync_mempools() # must be sure node 0 has received all txs
+ self.sync_mempools() # must be sure node 0 has received all txs
# Send one transaction from node0 that should be received, so that we
# we can sync the test on receipt (if node1's txs were relayed, they'd
@@ -91,14 +119,15 @@ class FeeFilterTest(BitcoinTestFramework):
# as well.
node0.settxfee(Decimal("0.00020000"))
txids = [node0.sendtoaddress(node0.getnewaddress(), 1)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
# Remove fee filter and check that txs are received again
- self.nodes[0].p2p.send_and_ping(msg_feefilter(0))
+ conn.send_and_ping(msg_feefilter(0))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
+
if __name__ == '__main__':
FeeFilterTest().main()
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py
index 5726a73e40..741da3be31 100755
--- a/test/functional/p2p_filter.py
+++ b/test/functional/p2p_filter.py
@@ -124,11 +124,11 @@ class FilterTest(BitcoinTestFramework):
self.log.info("Check that a node with bloom filters enabled services p2p mempool messages")
filter_peer = P2PBloomFilter()
- self.log.info("Create a tx relevant to the peer before connecting")
+ self.log.debug("Create a tx relevant to the peer before connecting")
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['addresses'][0]
txid = self.nodes[0].sendtoaddress(filter_address, 90)
- self.log.info("Send a mempool msg after connecting and check that the tx is received")
+ self.log.debug("Send a mempool msg after connecting and check that the tx is received")
self.nodes[0].add_p2p_connection(filter_peer)
filter_peer.send_and_ping(filter_peer.watch_filter_init)
self.nodes[0].p2p.send_message(msg_mempool())
@@ -227,8 +227,8 @@ class FilterTest(BitcoinTestFramework):
self.test_frelay_false(filter_peer_without_nrelay)
self.test_filter(filter_peer_without_nrelay)
- self.log.info('Test msg_mempool')
self.test_msg_mempool()
+
if __name__ == '__main__':
FilterTest().main()
diff --git a/test/functional/p2p_ibd_txrelay.py b/test/functional/p2p_ibd_txrelay.py
new file mode 100755
index 0000000000..c3e758b021
--- /dev/null
+++ b/test/functional/p2p_ibd_txrelay.py
@@ -0,0 +1,42 @@
+#!/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 fee filters during and after IBD."""
+
+from decimal import Decimal
+
+from test_framework.messages import COIN
+from test_framework.test_framework import BitcoinTestFramework
+
+MAX_FEE_FILTER = Decimal(9170997) / COIN
+NORMAL_FEE_FILTER = Decimal(100) / COIN
+
+
+class P2PIBDTxRelayTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+ self.extra_args = [
+ ["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
+ ["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
+ ]
+
+ def run_test(self):
+ self.log.info("Check that nodes set minfilter to MAX_MONEY while still in IBD")
+ for node in self.nodes:
+ assert node.getblockchaininfo()['initialblockdownload']
+ self.wait_until(lambda: all(peer['minfeefilter'] == MAX_FEE_FILTER for peer in node.getpeerinfo()))
+
+ # Come out of IBD by generating a block
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ self.log.info("Check that nodes reset minfilter after coming out of IBD")
+ for node in self.nodes:
+ assert not node.getblockchaininfo()['initialblockdownload']
+ self.wait_until(lambda: all(peer['minfeefilter'] == NORMAL_FEE_FILTER for peer in node.getpeerinfo()))
+
+
+if __name__ == '__main__':
+ P2PIBDTxRelayTest().main()
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index 81302374c9..d9a9ae5188 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -3,13 +3,13 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test node responses to invalid network messages."""
-import asyncio
-import struct
-import sys
from test_framework.messages import (
CBlockHeader,
CInv,
+ MAX_HEADERS_RESULTS,
+ MAX_INV_SIZE,
+ MAX_PROTOCOL_MESSAGE_LENGTH,
msg_getdata,
msg_headers,
msg_inv,
@@ -18,12 +18,16 @@ from test_framework.messages import (
ser_string,
)
from test_framework.mininode import (
- NetworkThread,
P2PDataStore,
P2PInterface,
)
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ wait_until,
+)
+VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix
class msg_unrecognized:
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
@@ -46,161 +50,71 @@ class InvalidMessagesTest(BitcoinTestFramework):
self.setup_clean_chain = True
def run_test(self):
- """
- . Test msg header
- 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see
- that it isn't an effective DoS against the node.
-
- 1. Send an oversized (4MB+) message and check that we're disconnected.
-
- 2. Send a few messages with an incorrect data size in the header, ensure the
- messages are ignored.
- """
+ self.test_buffer()
self.test_magic_bytes()
self.test_checksum()
self.test_size()
self.test_msgtype()
- self.test_large_inv()
-
- node = self.nodes[0]
- self.node = node
- node.add_p2p_connection(P2PDataStore())
- conn2 = node.add_p2p_connection(P2PDataStore())
-
- msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH
- valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix
-
- #
- # 0.
- #
- # Send as large a message as is valid, ensure we aren't disconnected but
- # also can't exhaust resources.
- #
- msg_at_size = msg_unrecognized(str_data="b" * valid_data_limit)
- assert len(msg_at_size.serialize()) == msg_limit
+ self.test_oversized_inv_msg()
+ self.test_oversized_getdata_msg()
+ self.test_oversized_headers_msg()
+ self.test_resource_exhaustion()
- self.log.info("Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...")
-
- # Run a bunch of times to test for memory exhaustion.
- for _ in range(80):
- node.p2p.send_message(msg_at_size)
-
- # Check that, even though the node is being hammered by nonsense from one
- # connection, it can still service other peers in a timely way.
- for _ in range(20):
- conn2.sync_with_ping(timeout=2)
-
- # Peer 1, despite serving up a bunch of nonsense, should still be connected.
- self.log.info("Waiting for node to drop junk messages.")
- node.p2p.sync_with_ping(timeout=400)
- assert node.p2p.is_connected
-
- #
- # 1.
- #
- # Send an oversized message, ensure we're disconnected.
- #
- # Under macOS this test is skipped due to an unexpected error code
- # returned from the closing socket which python/asyncio does not
- # yet know how to handle.
- #
- if sys.platform != 'darwin':
- msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1))
- assert len(msg_over_size.serialize()) == (msg_limit + 1)
-
- # An unknown message type (or *any* message type) over
- # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect.
- node.p2p.send_message(msg_over_size)
- node.p2p.wait_for_disconnect(timeout=4)
-
- node.disconnect_p2ps()
- conn = node.add_p2p_connection(P2PDataStore())
- conn.wait_for_verack()
- else:
- self.log.info("Skipping test p2p_invalid_messages/1 (oversized message) under macOS")
-
- #
- # 2.
- #
- # Send messages with an incorrect data size in the header.
- #
- actual_size = 100
- msg = msg_unrecognized(str_data="b" * actual_size)
-
- # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect.
- for wrong_size in (2, 77, 78, 79):
- self.log.info("Sending a message with incorrect size of {}".format(wrong_size))
-
- # Unmodified message should submit okay.
- node.p2p.send_and_ping(msg)
-
- # A message lying about its data size results in a disconnect when the incorrect
- # data size is less than the actual size.
- #
- # TODO: why does behavior change at 78 bytes?
- #
- node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size))
-
- # For some reason unknown to me, we sometimes have to push additional data to the
- # peer in order for it to realize a disconnect.
- try:
- node.p2p.send_message(msg_ping(nonce=123123))
- except IOError:
- pass
-
- node.p2p.wait_for_disconnect(timeout=10)
- node.disconnect_p2ps()
- node.add_p2p_connection(P2PDataStore())
-
- # Node is still up.
- conn = node.add_p2p_connection(P2PDataStore())
+ def test_buffer(self):
+ self.log.info("Test message with header split across two buffers is received")
+ conn = self.nodes[0].add_p2p_connection(P2PDataStore())
+ # Create valid message
+ msg = conn.build_message(msg_ping(nonce=12345))
+ cut_pos = 12 # Chosen at an arbitrary position within the header
+ # Send message in two pieces
+ before = int(self.nodes[0].getnettotals()['totalbytesrecv'])
+ conn.send_raw_message(msg[:cut_pos])
+ # Wait until node has processed the first half of the message
+ wait_until(lambda: int(self.nodes[0].getnettotals()['totalbytesrecv']) != before)
+ middle = int(self.nodes[0].getnettotals()['totalbytesrecv'])
+ # If this assert fails, we've hit an unlikely race
+ # where the test framework sent a message in between the two halves
+ assert_equal(middle, before + cut_pos)
+ conn.send_raw_message(msg[cut_pos:])
+ conn.sync_with_ping(timeout=1)
+ self.nodes[0].disconnect_p2ps()
def test_magic_bytes(self):
+ self.log.info("Test message with invalid magic bytes disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
-
- async def swap_magic_bytes():
- conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes
- conn.magic_bytes = b'\x00\x11\x22\x32'
-
- # Call .result() to block until the atomic swap is complete, otherwise
- # we might run into races later on
- asyncio.run_coroutine_threadsafe(swap_magic_bytes(), NetworkThread.network_event_loop).result()
-
- with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']):
- conn.send_message(msg_ping(nonce=0xff))
+ with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART badmsg']):
+ msg = conn.build_message(msg_unrecognized(str_data="d"))
+ # modify magic bytes
+ msg = b'\xff' * 4 + msg[4:]
+ conn.send_raw_message(msg)
conn.wait_for_disconnect(timeout=1)
- self.nodes[0].disconnect_p2ps()
+ self.nodes[0].disconnect_p2ps()
def test_checksum(self):
+ self.log.info("Test message with invalid checksum logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['CHECKSUM ERROR (badmsg, 2 bytes), expected 78df0a04 was ffffffff']):
msg = conn.build_message(msg_unrecognized(str_data="d"))
- cut_len = (
- 4 + # magic
- 12 + # msgtype
- 4 #len
- )
+ # Checksum is after start bytes (4B), message type (12B), len (4B)
+ cut_len = 4 + 12 + 4
# modify checksum
msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:]
- self.nodes[0].p2p.send_raw_message(msg)
+ conn.send_raw_message(msg)
conn.sync_with_ping(timeout=1)
- self.nodes[0].disconnect_p2ps()
+ self.nodes[0].disconnect_p2ps()
def test_size(self):
+ self.log.info("Test message with oversized payload disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['']):
- msg = conn.build_message(msg_unrecognized(str_data="d"))
- cut_len = (
- 4 + # magic
- 12 # msgtype
- )
- # modify len to MAX_SIZE + 1
- msg = msg[:cut_len] + struct.pack("<I", 0x02000000 + 1) + msg[cut_len + 4:]
- self.nodes[0].p2p.send_raw_message(msg)
+ msg = msg_unrecognized(str_data="d" * (VALID_DATA_LIMIT + 1))
+ msg = conn.build_message(msg)
+ conn.send_raw_message(msg)
conn.wait_for_disconnect(timeout=1)
- self.nodes[0].disconnect_p2ps()
+ self.nodes[0].disconnect_p2ps()
def test_msgtype(self):
+ self.log.info("Test message with invalid message type logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: ERRORS IN HEADER']):
msg = msg_unrecognized(str_data="d")
@@ -208,42 +122,53 @@ class InvalidMessagesTest(BitcoinTestFramework):
msg = conn.build_message(msg)
# Modify msgtype
msg = msg[:7] + b'\x00' + msg[7 + 1:]
- self.nodes[0].p2p.send_raw_message(msg)
+ conn.send_raw_message(msg)
conn.sync_with_ping(timeout=1)
- self.nodes[0].disconnect_p2ps()
-
- def test_large_inv(self):
- conn = self.nodes[0].add_p2p_connection(P2PInterface())
- with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (0 -> 20): message inv size() = 50001']):
- msg = msg_inv([CInv(MSG_TX, 1)] * 50001)
- conn.send_and_ping(msg)
- with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (20 -> 40): message getdata size() = 50001']):
- msg = msg_getdata([CInv(MSG_TX, 1)] * 50001)
- conn.send_and_ping(msg)
- with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (40 -> 60): headers message size = 2001']):
- msg = msg_headers([CBlockHeader()] * 2001)
- conn.send_and_ping(msg)
self.nodes[0].disconnect_p2ps()
- def _tweak_msg_data_size(self, message, wrong_size):
- """
- Return a raw message based on another message but with an incorrect data size in
- the message header.
- """
- raw_msg = self.node.p2p.build_message(message)
-
- bad_size_bytes = struct.pack("<I", wrong_size)
- num_header_bytes_before_size = 4 + 12
-
- # Replace the correct data size in the message with an incorrect one.
- raw_msg_with_wrong_size = (
- raw_msg[:num_header_bytes_before_size] +
- bad_size_bytes +
- raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):]
- )
- assert len(raw_msg) == len(raw_msg_with_wrong_size)
-
- return raw_msg_with_wrong_size
+ def test_oversized_msg(self, msg, size):
+ msg_type = msg.msgtype.decode('ascii')
+ self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size))
+ with self.nodes[0].assert_debug_log(['Misbehaving', '{} message size = {}'.format(msg_type, size)]):
+ self.nodes[0].add_p2p_connection(P2PInterface()).send_and_ping(msg)
+ self.nodes[0].disconnect_p2ps()
+
+ def test_oversized_inv_msg(self):
+ size = MAX_INV_SIZE + 1
+ self.test_oversized_msg(msg_inv([CInv(MSG_TX, 1)] * size), size)
+
+ def test_oversized_getdata_msg(self):
+ size = MAX_INV_SIZE + 1
+ self.test_oversized_msg(msg_getdata([CInv(MSG_TX, 1)] * size), size)
+
+ def test_oversized_headers_msg(self):
+ size = MAX_HEADERS_RESULTS + 1
+ self.test_oversized_msg(msg_headers([CBlockHeader()] * size), size)
+
+ def test_resource_exhaustion(self):
+ self.log.info("Test node stays up despite many large junk messages")
+ conn = self.nodes[0].add_p2p_connection(P2PDataStore())
+ conn2 = self.nodes[0].add_p2p_connection(P2PDataStore())
+ msg_at_size = msg_unrecognized(str_data="b" * VALID_DATA_LIMIT)
+ assert len(msg_at_size.serialize()) == MAX_PROTOCOL_MESSAGE_LENGTH
+
+ self.log.info("(a) Send 80 messages, each of maximum valid data size (4MB)")
+ for _ in range(80):
+ conn.send_message(msg_at_size)
+
+ # Check that, even though the node is being hammered by nonsense from one
+ # connection, it can still service other peers in a timely way.
+ self.log.info("(b) Check node still services peers in a timely way")
+ for _ in range(20):
+ conn2.sync_with_ping(timeout=2)
+
+ self.log.info("(c) Wait for node to drop junk messages, while remaining connected")
+ conn.sync_with_ping(timeout=400)
+
+ # Despite being served up a bunch of nonsense, the peers should still be connected.
+ assert conn.is_connected
+ assert conn2.is_connected
+ self.nodes[0].disconnect_p2ps()
if __name__ == '__main__':
diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py
index 157af68203..fe6e236fc4 100755
--- a/test/functional/p2p_leak.py
+++ b/test/functional/p2p_leak.py
@@ -26,7 +26,7 @@ from test_framework.util import (
wait_until,
)
-banscore = 10
+DISCOURAGEMENT_THRESHOLD = 100
class CLazyNode(P2PInterface):
@@ -65,12 +65,13 @@ class CLazyNode(P2PInterface):
# Node that never sends a version. We'll use this to send a bunch of messages
# anyway, and eventually get disconnected.
-class CNodeNoVersionBan(CLazyNode):
- # send a bunch of veracks without sending a message. This should get us disconnected.
- # NOTE: implementation-specific check here. Remove if bitcoind ban behavior changes
+class CNodeNoVersionMisbehavior(CLazyNode):
+ # Send enough veracks without a message to reach the peer discouragement
+ # threshold. This should get us disconnected. NOTE: implementation-specific
+ # test; update if our discouragement policy for peer misbehavior changes.
def on_open(self):
super().on_open()
- for i in range(banscore):
+ for _ in range(DISCOURAGEMENT_THRESHOLD):
self.send_message(msg_verack())
# Node that never sends a version. This one just sits idle and hopes to receive
@@ -106,10 +107,10 @@ class P2PVersionStore(P2PInterface):
class P2PLeakTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.extra_args = [['-banscore=' + str(banscore)]]
def run_test(self):
- no_version_bannode = self.nodes[0].add_p2p_connection(CNodeNoVersionBan(), send_version=False, wait_for_verack=False)
+ no_version_disconnect_node = self.nodes[0].add_p2p_connection(
+ CNodeNoVersionMisbehavior(), send_version=False, wait_for_verack=False)
no_version_idlenode = self.nodes[0].add_p2p_connection(CNodeNoVersionIdle(), send_version=False, wait_for_verack=False)
no_verack_idlenode = self.nodes[0].add_p2p_connection(CNodeNoVerackIdle(), wait_for_verack=False)
@@ -117,7 +118,7 @@ class P2PLeakTest(BitcoinTestFramework):
# verack, since we never sent one
no_verack_idlenode.wait_for_verack()
- wait_until(lambda: no_version_bannode.ever_connected, timeout=10, lock=mininode_lock)
+ wait_until(lambda: no_version_disconnect_node.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: no_version_idlenode.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: no_verack_idlenode.version_received, timeout=10, lock=mininode_lock)
@@ -127,16 +128,13 @@ class P2PLeakTest(BitcoinTestFramework):
#Give the node enough time to possibly leak out a message
time.sleep(5)
- #This node should have been banned
- assert not no_version_bannode.is_connected
+ # Expect this node to be disconnected for misbehavior
+ assert not no_version_disconnect_node.is_connected
self.nodes[0].disconnect_p2ps()
- # Wait until all connections are closed
- wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 0)
-
# Make sure no unexpected messages came in
- assert no_version_bannode.unexpected_msg == False
+ assert no_version_disconnect_node.unexpected_msg == False
assert no_version_idlenode.unexpected_msg == False
assert no_verack_idlenode.unexpected_msg == False
diff --git a/test/functional/p2p_nobloomfilter_messages.py b/test/functional/p2p_nobloomfilter_messages.py
index 41af74ebb8..accc5dc23c 100755
--- a/test/functional/p2p_nobloomfilter_messages.py
+++ b/test/functional/p2p_nobloomfilter_messages.py
@@ -4,40 +4,45 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test invalid p2p messages for nodes with bloom filters disabled.
-Test that, when bloom filters are not enabled, nodes are disconnected if:
+Test that, when bloom filters are not enabled, peers are disconnected if:
1. They send a p2p mempool message
2. They send a p2p filterload message
3. They send a p2p filteradd message
+4. They send a p2p filterclear message
"""
-from test_framework.messages import msg_mempool, msg_filteradd, msg_filterload
+from test_framework.messages import msg_mempool, msg_filteradd, msg_filterload, msg_filterclear
from test_framework.mininode import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
-class P2PNobloomfilterMessages(BitcoinTestFramework):
+class P2PNoBloomFilterMessages(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [["-peerbloomfilters=0"]]
def test_message_causes_disconnect(self, message):
- # Add a p2p connection that sends a message and check that it disconnects
+ """Add a p2p connection that sends a message and check that it disconnects."""
peer = self.nodes[0].add_p2p_connection(P2PInterface())
peer.send_message(message)
peer.wait_for_disconnect()
- assert_equal(len(self.nodes[0].getpeerinfo()), 0)
+ assert_equal(self.nodes[0].getconnectioncount(), 0)
def run_test(self):
- self.log.info("Test that node is disconnected if it sends mempool message")
+ self.log.info("Test that peer is disconnected if it sends mempool message")
self.test_message_causes_disconnect(msg_mempool())
- self.log.info("Test that node is disconnected if it sends filterload message")
+ self.log.info("Test that peer is disconnected if it sends filterload message")
self.test_message_causes_disconnect(msg_filterload())
- self.log.info("Test that node is disconnected if it sends filteradd message")
+ self.log.info("Test that peer is disconnected if it sends filteradd message")
self.test_message_causes_disconnect(msg_filteradd(data=b'\xcc'))
+ self.log.info("Test that peer is disconnected if it sends a filterclear message")
+ self.test_message_causes_disconnect(msg_filterclear())
+
+
if __name__ == '__main__':
- P2PNobloomfilterMessages().main()
+ P2PNoBloomFilterMessages().main()
diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py
index ed3429a037..a2f6ea538c 100755
--- a/test/functional/p2p_node_network_limited.py
+++ b/test/functional/p2p_node_network_limited.py
@@ -83,7 +83,6 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
assert_equal(node1.firstAddrnServices, expected_services)
self.nodes[0].disconnect_p2ps()
- node1.wait_for_disconnect()
# connect unsynced node 2 with pruned NODE_NETWORK_LIMITED peer
# because node 2 is in IBD and node 0 is a NODE_NETWORK_LIMITED peer, sync must not be possible
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 2c200fccad..32a795e345 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -39,13 +39,20 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.checkpermission(
# default permissions (no specific permissions)
["-whitelist=127.0.0.1"],
- ["relay", "noban", "mempool"],
+ # Make sure the default values in the command line documentation match the ones here
+ ["relay", "noban", "mempool", "download"],
True)
self.checkpermission(
+ # no permission (even with forcerelay)
+ ["-whitelist=@127.0.0.1", "-whitelistforcerelay=1"],
+ [],
+ False)
+
+ self.checkpermission(
# relay permission removed (no specific permissions)
["-whitelist=127.0.0.1", "-whitelistrelay=0"],
- ["noban", "mempool"],
+ ["noban", "mempool", "download"],
True)
self.checkpermission(
@@ -53,7 +60,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
# Legacy parameter interaction which set whitelistrelay to true
# if whitelistforcerelay is true
["-whitelist=127.0.0.1", "-whitelistforcerelay"],
- ["forcerelay", "relay", "noban", "mempool"],
+ ["forcerelay", "relay", "noban", "mempool", "download"],
True)
# Let's make sure permissions are merged correctly
@@ -64,32 +71,32 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.checkpermission(
["-whitelist=noban@127.0.0.1"],
# Check parameter interaction forcerelay should activate relay
- ["noban", "bloomfilter", "forcerelay", "relay"],
+ ["noban", "bloomfilter", "forcerelay", "relay", "download"],
False)
self.replaceinconfig(1, "whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1")
self.checkpermission(
# legacy whitelistrelay should be ignored
["-whitelist=noban,mempool@127.0.0.1", "-whitelistrelay"],
- ["noban", "mempool"],
+ ["noban", "mempool", "download"],
False)
self.checkpermission(
# legacy whitelistforcerelay should be ignored
["-whitelist=noban,mempool@127.0.0.1", "-whitelistforcerelay"],
- ["noban", "mempool"],
+ ["noban", "mempool", "download"],
False)
self.checkpermission(
# missing mempool permission to be considered legacy whitelisted
["-whitelist=noban@127.0.0.1"],
- ["noban"],
+ ["noban", "download"],
False)
self.checkpermission(
# all permission added
["-whitelist=all@127.0.0.1"],
- ["forcerelay", "noban", "mempool", "bloomfilter", "relay"],
+ ["forcerelay", "noban", "mempool", "bloomfilter", "relay", "download"],
False)
self.stop_node(1)
@@ -101,9 +108,9 @@ class P2PPermissionsTests(BitcoinTestFramework):
block_op_true = self.nodes[0].getblock(self.nodes[0].generatetoaddress(100, ADDRESS_BCRT1_P2WSH_OP_TRUE)[0])
self.sync_all()
- self.log.debug("Create a connection from a whitelisted wallet that rebroadcasts raw txs")
+ self.log.debug("Create a connection from a forcerelay peer that rebroadcasts raw txs")
# A python mininode is needed to send the raw transaction directly. If a full node was used, it could only
- # rebroadcast via the inv-getdata mechanism. However, even for whitelisted connections, a full node would
+ # rebroadcast via the inv-getdata mechanism. However, even for forcerelay connections, a full node would
# currently not request a txid that is already in the mempool.
self.restart_node(1, extra_args=["-whitelist=forcerelay@127.0.0.1"])
p2p_rebroadcast_wallet = self.nodes[1].add_p2p_connection(P2PDataStore())
@@ -128,7 +135,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.log.debug("Check that node[1] will send the tx to node[0] even though it is already in the mempool")
connect_nodes(self.nodes[1], 0)
- with self.nodes[1].assert_debug_log(["Force relaying tx {} from whitelisted peer=0".format(txid)]):
+ with self.nodes[1].assert_debug_log(["Force relaying tx {} from peer=0".format(txid)]):
p2p_rebroadcast_wallet.send_txs_and_test([tx], self.nodes[1])
wait_until(lambda: txid in self.nodes[0].getrawmempool())
@@ -139,7 +146,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
[tx],
self.nodes[1],
success=False,
- reject_reason='Not relaying non-mempool transaction {} from whitelisted peer=0'.format(txid),
+ reject_reason='Not relaying non-mempool transaction {} from forcerelay peer=0'.format(txid),
)
def checkpermission(self, args, expectedPermissions, whitelisted):
diff --git a/test/functional/p2p_ping.py b/test/functional/p2p_ping.py
new file mode 100755
index 0000000000..e00af88cc4
--- /dev/null
+++ b/test/functional/p2p_ping.py
@@ -0,0 +1,123 @@
+#!/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 ping message
+"""
+
+import time
+
+from test_framework.messages import (
+ msg_pong,
+)
+from test_framework.mininode import (
+ P2PInterface,
+ wait_until,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+PING_INTERVAL = 2 * 60
+
+
+class msg_pong_corrupt(msg_pong):
+ def serialize(self):
+ return b""
+
+
+class NodePongAdd1(P2PInterface):
+ def on_ping(self, message):
+ self.send_message(msg_pong(message.nonce + 1))
+
+
+class NodeNoPong(P2PInterface):
+ def on_ping(self, message):
+ pass
+
+
+class PingPongTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ self.extra_args = [['-peertimeout=3']]
+
+ def check_peer_info(self, *, pingtime, minping, pingwait):
+ stats = self.nodes[0].getpeerinfo()[0]
+ assert_equal(stats.pop('pingtime', None), pingtime)
+ assert_equal(stats.pop('minping', None), minping)
+ assert_equal(stats.pop('pingwait', None), pingwait)
+
+ def mock_forward(self, delta):
+ self.mock_time += delta
+ self.nodes[0].setmocktime(self.mock_time)
+
+ def run_test(self):
+ self.mock_time = int(time.time())
+ self.mock_forward(0)
+
+ self.log.info('Check that ping is sent after connection is established')
+ no_pong_node = self.nodes[0].add_p2p_connection(NodeNoPong())
+ self.mock_forward(3)
+ assert no_pong_node.last_message.pop('ping').nonce != 0
+ self.check_peer_info(pingtime=None, minping=None, pingwait=3)
+
+ self.log.info('Reply without nonce cancels ping')
+ with self.nodes[0].assert_debug_log(['pong peer=0: Short payload']):
+ no_pong_node.send_and_ping(msg_pong_corrupt())
+ self.check_peer_info(pingtime=None, minping=None, pingwait=None)
+
+ self.log.info('Reply without ping')
+ with self.nodes[0].assert_debug_log([
+ 'pong peer=0: Unsolicited pong without ping, 0 expected, 0 received, 8 bytes',
+ ]):
+ no_pong_node.send_and_ping(msg_pong())
+ self.check_peer_info(pingtime=None, minping=None, pingwait=None)
+
+ self.log.info('Reply with wrong nonce does not cancel ping')
+ assert 'ping' not in no_pong_node.last_message
+ with self.nodes[0].assert_debug_log(['pong peer=0: Nonce mismatch']):
+ # mock time PING_INTERVAL ahead to trigger node into sending a ping
+ self.mock_forward(PING_INTERVAL + 1)
+ wait_until(lambda: 'ping' in no_pong_node.last_message)
+ self.mock_forward(9)
+ # Send the wrong pong
+ no_pong_node.send_and_ping(msg_pong(no_pong_node.last_message.pop('ping').nonce - 1))
+ self.check_peer_info(pingtime=None, minping=None, pingwait=9)
+
+ self.log.info('Reply with zero nonce does cancel ping')
+ with self.nodes[0].assert_debug_log(['pong peer=0: Nonce zero']):
+ no_pong_node.send_and_ping(msg_pong(0))
+ self.check_peer_info(pingtime=None, minping=None, pingwait=None)
+
+ self.log.info('Check that ping is properly reported on RPC')
+ assert 'ping' not in no_pong_node.last_message
+ # mock time PING_INTERVAL ahead to trigger node into sending a ping
+ self.mock_forward(PING_INTERVAL + 1)
+ wait_until(lambda: 'ping' in no_pong_node.last_message)
+ ping_delay = 29
+ self.mock_forward(ping_delay)
+ wait_until(lambda: 'ping' in no_pong_node.last_message)
+ no_pong_node.send_and_ping(msg_pong(no_pong_node.last_message.pop('ping').nonce))
+ self.check_peer_info(pingtime=ping_delay, minping=ping_delay, pingwait=None)
+
+ self.log.info('Check that minping is decreased after a fast roundtrip')
+ # mock time PING_INTERVAL ahead to trigger node into sending a ping
+ self.mock_forward(PING_INTERVAL + 1)
+ wait_until(lambda: 'ping' in no_pong_node.last_message)
+ ping_delay = 9
+ self.mock_forward(ping_delay)
+ wait_until(lambda: 'ping' in no_pong_node.last_message)
+ no_pong_node.send_and_ping(msg_pong(no_pong_node.last_message.pop('ping').nonce))
+ self.check_peer_info(pingtime=ping_delay, minping=ping_delay, pingwait=None)
+
+ self.log.info('Check that peer is disconnected after ping timeout')
+ assert 'ping' not in no_pong_node.last_message
+ self.nodes[0].ping()
+ wait_until(lambda: 'ping' in no_pong_node.last_message)
+ with self.nodes[0].assert_debug_log(['ping timeout: 1201.000000s']):
+ self.mock_forward(20 * 60 + 1)
+ time.sleep(4) # peertimeout + 1
+
+
+if __name__ == '__main__':
+ PingPongTest().main()
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 8803086213..9915b844d1 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -25,6 +25,7 @@ from test_framework.messages import (
MSG_BLOCK,
MSG_TX,
MSG_WITNESS_FLAG,
+ MSG_WTX,
NODE_NETWORK,
NODE_WITNESS,
msg_no_witness_block,
@@ -34,6 +35,7 @@ from test_framework.messages import (
msg_tx,
msg_block,
msg_no_witness_tx,
+ msg_verack,
ser_uint256,
ser_vector,
sha256,
@@ -81,6 +83,7 @@ from test_framework.util import (
softfork_active,
hex_str_to_bytes,
assert_raises_rpc_error,
+ wait_until,
)
# The versionbit bit used to signal activation of SegWit
@@ -143,25 +146,47 @@ def test_witness_block(node, p2p, block, accepted, with_witness=True, reason=Non
class TestP2PConn(P2PInterface):
- def __init__(self):
+ def __init__(self, wtxidrelay=False):
super().__init__()
self.getdataset = set()
+ self.last_wtxidrelay = []
+ self.lastgetdata = []
+ self.wtxidrelay = wtxidrelay
# Avoid sending out msg_getdata in the mininode thread as a reply to invs.
# They are not needed and would only lead to races because we send msg_getdata out in the test thread
def on_inv(self, message):
pass
+ def on_version(self, message):
+ if self.wtxidrelay:
+ super().on_version(message)
+ else:
+ self.send_message(msg_verack())
+ self.nServices = message.nServices
+
def on_getdata(self, message):
+ self.lastgetdata = message.inv
for inv in message.inv:
self.getdataset.add(inv.hash)
- def announce_tx_and_wait_for_getdata(self, tx, timeout=60, success=True):
+ def on_wtxidrelay(self, message):
+ self.last_wtxidrelay.append(message)
+
+ def announce_tx_and_wait_for_getdata(self, tx, timeout=60, success=True, use_wtxid=False):
with mininode_lock:
self.last_message.pop("getdata", None)
- self.send_message(msg_inv(inv=[CInv(MSG_TX, tx.sha256)]))
+ if use_wtxid:
+ wtxid = tx.calc_sha256(True)
+ self.send_message(msg_inv(inv=[CInv(MSG_WTX, wtxid)]))
+ else:
+ self.send_message(msg_inv(inv=[CInv(MSG_TX, tx.sha256)]))
+
if success:
- self.wait_for_getdata([tx.sha256], timeout)
+ if use_wtxid:
+ self.wait_for_getdata([wtxid], timeout)
+ else:
+ self.wait_for_getdata([tx.sha256], timeout)
else:
time.sleep(timeout)
assert not self.last_message.get("getdata")
@@ -277,6 +302,7 @@ class SegWitTest(BitcoinTestFramework):
self.test_upgrade_after_activation()
self.test_witness_sigops()
self.test_superfluous_witness()
+ self.test_wtxid_relay()
# Individual tests
@@ -1270,7 +1296,6 @@ class SegWitTest(BitcoinTestFramework):
test_transaction_acceptance(self.nodes[0], self.test_node, tx, with_witness=True, accepted=False)
# Verify that removing the witness succeeds.
- self.test_node.announce_tx_and_wait_for_getdata(tx)
test_transaction_acceptance(self.nodes[0], self.test_node, tx, with_witness=False, accepted=True)
# Now try to add extra witness data to a valid witness tx.
@@ -1297,8 +1322,6 @@ class SegWitTest(BitcoinTestFramework):
# Node will not be blinded to the transaction
self.std_node.announce_tx_and_wait_for_getdata(tx3)
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False, 'tx-size')
- self.std_node.announce_tx_and_wait_for_getdata(tx3)
- test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False, 'tx-size')
# Remove witness stuffing, instead add extra witness push on stack
tx3.vout[0] = CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))
@@ -1898,8 +1921,7 @@ class SegWitTest(BitcoinTestFramework):
def test_upgrade_after_activation(self):
"""Test the behavior of starting up a segwit-aware node after the softfork has activated."""
- self.stop_node(2)
- self.start_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
+ self.restart_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
connect_nodes(self.nodes[0], 2)
# We reconnect more than 100 blocks, give it plenty of time
@@ -2017,6 +2039,11 @@ class SegWitTest(BitcoinTestFramework):
# TODO: test p2sh sigop counting
+ # Cleanup and prep for next test
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+ @subtest # type: ignore
def test_superfluous_witness(self):
# Serialization of tx that puts witness flag to 3 always
def serialize_with_bogus_witness(tx):
@@ -2060,6 +2087,67 @@ class SegWitTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(['Unknown transaction optional data']):
self.nodes[0].p2p.send_and_ping(msg_bogus_tx(tx))
+ @subtest # type: ignore
+ def test_wtxid_relay(self):
+ # Use brand new nodes to avoid contamination from earlier tests
+ self.wtx_node = self.nodes[0].add_p2p_connection(TestP2PConn(wtxidrelay=True), services=NODE_NETWORK | NODE_WITNESS)
+ self.tx_node = self.nodes[0].add_p2p_connection(TestP2PConn(wtxidrelay=False), services=NODE_NETWORK | NODE_WITNESS)
+
+ # Check wtxidrelay feature negotiation message through connecting a new peer
+ def received_wtxidrelay():
+ return (len(self.wtx_node.last_wtxidrelay) > 0)
+ wait_until(received_wtxidrelay, timeout=60, lock=mininode_lock)
+
+ # Create a Segwit output from the latest UTXO
+ # and announce it to the network
+ witness_program = CScript([OP_TRUE])
+ witness_hash = sha256(witness_program)
+ script_pubkey = CScript([OP_0, witness_hash])
+
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey))
+ tx.rehash()
+
+ # Create a Segwit transaction
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_pubkey))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program]
+ tx2.rehash()
+
+ # Announce Segwit transaction with wtxid
+ # and wait for getdata
+ self.wtx_node.announce_tx_and_wait_for_getdata(tx2, use_wtxid=True)
+ with mininode_lock:
+ lgd = self.wtx_node.lastgetdata[:]
+ assert_equal(lgd, [CInv(MSG_WTX, tx2.calc_sha256(True))])
+
+ # Announce Segwit transaction from non wtxidrelay peer
+ # and wait for getdata
+ self.tx_node.announce_tx_and_wait_for_getdata(tx2, use_wtxid=False)
+ with mininode_lock:
+ lgd = self.tx_node.lastgetdata[:]
+ assert_equal(lgd, [CInv(MSG_TX|MSG_WITNESS_FLAG, tx2.sha256)])
+
+ # Send tx2 through; it's an orphan so won't be accepted
+ with mininode_lock:
+ self.tx_node.last_message.pop("getdata", None)
+ test_transaction_acceptance(self.nodes[0], self.tx_node, tx2, with_witness=True, accepted=False)
+
+ # Expect a request for parent (tx) due to use of non-WTX peer
+ self.tx_node.wait_for_getdata([tx.sha256], 60)
+ with mininode_lock:
+ lgd = self.tx_node.lastgetdata[:]
+ assert_equal(lgd, [CInv(MSG_TX|MSG_WITNESS_FLAG, tx.sha256)])
+
+ # Send tx through
+ test_transaction_acceptance(self.nodes[0], self.tx_node, tx, with_witness=False, accepted=True)
+
+ # Check tx2 is there now
+ assert_equal(tx2.hash in self.nodes[0].getrawmempool(), True)
+
if __name__ == '__main__':
SegWitTest().main()
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 10f5eea0e5..2527edc135 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -12,6 +12,7 @@ from test_framework.messages import (
FromHex,
MSG_TX,
MSG_TYPE_MASK,
+ MSG_WTX,
msg_inv,
msg_notfound,
)
@@ -36,7 +37,7 @@ class TestP2PConn(P2PInterface):
def on_getdata(self, message):
for i in message.inv:
- if i.type & MSG_TYPE_MASK == MSG_TX:
+ if i.type & MSG_TYPE_MASK == MSG_TX or i.type & MSG_TYPE_MASK == MSG_WTX:
self.tx_getdata_count += 1
@@ -44,12 +45,13 @@ class TestP2PConn(P2PInterface):
GETDATA_TX_INTERVAL = 60 # seconds
MAX_GETDATA_RANDOM_DELAY = 2 # seconds
INBOUND_PEER_TX_DELAY = 2 # seconds
+TXID_RELAY_DELAY = 2 # seconds
MAX_GETDATA_IN_FLIGHT = 100
TX_EXPIRY_INTERVAL = GETDATA_TX_INTERVAL * 10
# Python test constants
NUM_INBOUND = 10
-MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY
+MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY + TXID_RELAY_DELAY
class TxDownloadTest(BitcoinTestFramework):
@@ -63,7 +65,7 @@ class TxDownloadTest(BitcoinTestFramework):
txid = 0xdeadbeef
self.log.info("Announce the txid from each incoming peer to node 0")
- msg = msg_inv([CInv(t=MSG_TX, h=txid)])
+ msg = msg_inv([CInv(t=MSG_WTX, h=txid)])
for p in self.nodes[0].p2ps:
p.send_and_ping(msg)
@@ -135,13 +137,13 @@ class TxDownloadTest(BitcoinTestFramework):
with mininode_lock:
p.tx_getdata_count = 0
- p.send_message(msg_inv([CInv(t=MSG_TX, h=i) for i in txids]))
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=i) for i in txids]))
wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT, lock=mininode_lock)
with mininode_lock:
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT)
self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request")
- p.send_message(msg_notfound(vec=[CInv(t=MSG_TX, h=txids[0])]))
+ p.send_message(msg_notfound(vec=[CInv(t=MSG_WTX, h=txids[0])]))
wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT + 1, timeout=10, lock=mininode_lock)
with mininode_lock:
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1)
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index c323168848..71b0b0f63a 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test processing of unrequested blocks.
-Setup: two nodes, node0+node1, not connected to each other. Node1 will have
+Setup: two nodes, node0 + node1, not connected to each other. Node1 will have
nMinimumChainWork set to 0x10, so it won't process low-work unrequested blocks.
We have one P2PInterface connection to node0 called test_node, and one to node1
@@ -71,18 +71,10 @@ class AcceptBlockTest(BitcoinTestFramework):
self.extra_args = [[], ["-minimumchainwork=0x10"]]
def setup_network(self):
- # Node0 will be used to test behavior of processing unrequested blocks
- # from peers which are not whitelisted, while Node1 will be used for
- # the whitelisted case.
- # Node2 will be used for non-whitelisted peers to test the interaction
- # with nMinimumChainWork.
self.setup_nodes()
def run_test(self):
- # Setup the p2p connections
- # test_node connects to node0 (not whitelisted)
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
- # min_work_node connects to node1 (whitelisted)
min_work_node = self.nodes[1].add_p2p_connection(P2PInterface())
# 1. Have nodes mine a block (leave IBD)
@@ -226,7 +218,7 @@ class AcceptBlockTest(BitcoinTestFramework):
self.nodes[0].getblock(all_blocks[286].hash)
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash)
- self.log.info("Successfully reorged to longer chain from non-whitelisted peer")
+ self.log.info("Successfully reorged to longer chain")
# 8. Create a chain which is invalid at a height longer than the
# current chain, but which has more blocks on top of that
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 6273c229ae..7c70f30ca3 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -241,6 +241,17 @@ class BlockchainTest(BitcoinTestFramework):
del res['disk_size'], res3['disk_size']
assert_equal(res, res3)
+ self.log.info("Test hash_type option for gettxoutsetinfo()")
+ # Adding hash_type 'hash_serialized_2', which is the default, should
+ # not change the result.
+ res4 = node.gettxoutsetinfo(hash_type='hash_serialized_2')
+ del res4['disk_size']
+ assert_equal(res, res4)
+
+ # hash_type none should not return a UTXO set hash.
+ res5 = node.gettxoutsetinfo(hash_type='none')
+ assert 'hash_serialized_2' not in res5
+
def _test_getblockheader(self):
node = self.nodes[0]
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 4bc4913bda..57c8f511ac 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -271,7 +271,11 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex'])
+ # Should fail without add_inputs:
+ assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
+ # add_inputs is enabled by default
rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
totalOut = 0
matchingOuts = 0
@@ -299,7 +303,10 @@ class RawTransactionsTest(BitcoinTestFramework):
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
- rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ # Should fail without add_inputs:
+ assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {"add_inputs": True})
+
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
totalOut = 0
matchingOuts = 0
@@ -330,7 +337,10 @@ class RawTransactionsTest(BitcoinTestFramework):
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
- rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ # Should fail without add_inputs:
+ assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx, {"add_inputs": False})
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {"add_inputs": True})
+
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
totalOut = 0
matchingOuts = 0
diff --git a/test/functional/rpc_getaddressinfo_label_deprecation.py b/test/functional/rpc_getaddressinfo_label_deprecation.py
deleted file mode 100755
index 09545ebce7..0000000000
--- a/test/functional/rpc_getaddressinfo_label_deprecation.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2020-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.
-"""
-Test deprecation of the RPC getaddressinfo `label` field. It has been
-superseded by the `labels` field.
-
-"""
-from test_framework.test_framework import BitcoinTestFramework
-
-class GetAddressInfoLabelDeprecationTest(BitcoinTestFramework):
- def set_test_params(self):
- self.num_nodes = 2
- self.setup_clean_chain = False
- # Start node[0] with -deprecatedrpc=label, and node[1] without.
- self.extra_args = [["-deprecatedrpc=label"], []]
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
- def test_label_with_deprecatedrpc_flag(self):
- self.log.info("Test getaddressinfo label with -deprecatedrpc flag")
- node = self.nodes[0]
- address = node.getnewaddress()
- info = node.getaddressinfo(address)
- assert "label" in info
-
- def test_label_without_deprecatedrpc_flag(self):
- self.log.info("Test getaddressinfo label without -deprecatedrpc flag")
- node = self.nodes[1]
- address = node.getnewaddress()
- info = node.getaddressinfo(address)
- assert "label" not in info
-
- def run_test(self):
- """Test getaddressinfo label with and without -deprecatedrpc flag."""
- self.test_label_with_deprecatedrpc_flag()
- self.test_label_without_deprecatedrpc_flag()
-
-
-if __name__ == '__main__':
- GetAddressInfoLabelDeprecationTest().main()
diff --git a/test/functional/rpc_getaddressinfo_labels_purpose_deprecation.py b/test/functional/rpc_getaddressinfo_labels_purpose_deprecation.py
deleted file mode 100755
index 903f5536b9..0000000000
--- a/test/functional/rpc_getaddressinfo_labels_purpose_deprecation.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/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 deprecation of RPC getaddressinfo `labels` returning an array
-containing a JSON object of `name` and purpose` key-value pairs. It now
-returns an array containing only the label name.
-
-"""
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal
-
-LABELS_TO_TEST = frozenset({"" , "New 𝅘𝅥𝅯 $<#>&!рыба Label"})
-
-class GetAddressInfoLabelsPurposeDeprecationTest(BitcoinTestFramework):
- def set_test_params(self):
- self.num_nodes = 2
- self.setup_clean_chain = False
- # Start node[0] with -deprecatedrpc=labelspurpose and node[1] without.
- self.extra_args = [["-deprecatedrpc=labelspurpose"], []]
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
- def test_labels(self, node_num, label_name, expected_value):
- node = self.nodes[node_num]
- address = node.getnewaddress()
- if label_name != "":
- node.setlabel(address, label_name)
- self.log.info(" set label to {}".format(label_name))
- labels = node.getaddressinfo(address)["labels"]
- self.log.info(" labels = {}".format(labels))
- assert_equal(labels, expected_value)
-
- def run_test(self):
- """Test getaddressinfo labels with and without -deprecatedrpc flag."""
- self.log.info("Test getaddressinfo labels with -deprecatedrpc flag")
- for label in LABELS_TO_TEST:
- self.test_labels(node_num=0, label_name=label, expected_value=[{"name": label, "purpose": "receive"}])
-
- self.log.info("Test getaddressinfo labels without -deprecatedrpc flag")
- for label in LABELS_TO_TEST:
- self.test_labels(node_num=1, label_name=label, expected_value=[label])
-
-
-if __name__ == '__main__':
- GetAddressInfoLabelsPurposeDeprecationTest().main()
diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py
index bd93b6f7a4..8fa36445cd 100755
--- a/test/functional/rpc_getblockfilter.py
+++ b/test/functional/rpc_getblockfilter.py
@@ -7,7 +7,7 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal, assert_is_hex_string, assert_raises_rpc_error,
- connect_nodes, disconnect_nodes, sync_blocks
+ connect_nodes, disconnect_nodes
)
FILTER_TYPES = ["basic"]
@@ -30,7 +30,7 @@ class GetBlockFilterTest(BitcoinTestFramework):
# Reorg node 0 to a new chain
connect_nodes(self.nodes[0], 1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
assert_equal(self.nodes[0].getblockcount(), 4)
chain1_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
diff --git a/test/functional/rpc_getpeerinfo_banscore_deprecation.py b/test/functional/rpc_getpeerinfo_banscore_deprecation.py
new file mode 100755
index 0000000000..b830248e1e
--- /dev/null
+++ b/test/functional/rpc_getpeerinfo_banscore_deprecation.py
@@ -0,0 +1,24 @@
+#!/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 deprecation of getpeerinfo RPC banscore field."""
+
+from test_framework.test_framework import BitcoinTestFramework
+
+
+class GetpeerinfoBanscoreDeprecationTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.extra_args = [[], ["-deprecatedrpc=banscore"]]
+
+ def run_test(self):
+ self.log.info("Test getpeerinfo by default no longer returns a banscore field")
+ assert "banscore" not in self.nodes[0].getpeerinfo()[0].keys()
+
+ self.log.info("Test getpeerinfo returns banscore with -deprecatedrpc=banscore")
+ assert "banscore" in self.nodes[1].getpeerinfo()[0].keys()
+
+
+if __name__ == "__main__":
+ GetpeerinfoBanscoreDeprecationTest().main()
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 58d8c4abe1..3336246c8b 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -46,10 +46,12 @@ class NetTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
- self.extra_args = [["-minrelaytxfee=0.00001000"],["-minrelaytxfee=0.00000500"]]
+ self.extra_args = [["-minrelaytxfee=0.00001000"], ["-minrelaytxfee=0.00000500"]]
self.supports_cli = False
def run_test(self):
+ self.log.info('Get out of IBD for the minfeefilter test')
+ self.nodes[0].generate(1)
self.log.info('Connect nodes both way')
connect_nodes(self.nodes[0], 1)
connect_nodes(self.nodes[1], 0)
@@ -100,12 +102,14 @@ class NetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
- self.nodes[0].setnetworkactive(state=False)
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.nodes[0].setnetworkactive(state=False)
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False)
# Wait a bit for all sockets to close
wait_until(lambda: self.nodes[0].getnetworkinfo()['connections'] == 0, timeout=3)
- self.nodes[0].setnetworkactive(state=True)
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.nodes[0].setnetworkactive(state=True)
self.log.info('Connect nodes both way')
connect_nodes(self.nodes[0], 1)
connect_nodes(self.nodes[1], 0)
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 9b07c39606..4d985dd1b1 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -8,6 +8,7 @@
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
+ assert_approx,
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
@@ -37,6 +38,7 @@ class PSBTTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ # TODO: Re-enable this test with segwit v1
def test_utxo_conversion(self):
mining_node = self.nodes[2]
offline_node = self.nodes[0]
@@ -85,6 +87,13 @@ class PSBTTest(BitcoinTestFramework):
# Create and fund a raw tx for sending 10 BTC
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
+ # If inputs are specified, do not automatically add more:
+ utxo1 = self.nodes[0].listunspent()[0]
+ assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90})
+
+ psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90}, 0, {"add_inputs": True})['psbt']
+ assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['tx']['vin']), 2)
+
# Node 1 should not be able to add anything to it but still return the psbtx same as before
psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt']
assert_equal(psbtx1, psbtx)
@@ -146,19 +155,25 @@ class PSBTTest(BitcoinTestFramework):
p2pkh_pos = out['n']
# spend single key from node 1
- rawtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt']
- walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(rawtx)
+ created_psbt = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99})
+ walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(created_psbt['psbt'])
+ # Make sure it has both types of UTXOs
+ decoded = self.nodes[1].decodepsbt(walletprocesspsbt_out['psbt'])
+ assert 'non_witness_utxo' in decoded['inputs'][0]
+ assert 'witness_utxo' in decoded['inputs'][0]
+ # Check decodepsbt fee calculation (input values shall only be counted once per UTXO)
+ assert_equal(decoded['fee'], created_psbt['fee'])
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
# feeRate of 0.1 BTC / KB produces a total fee slightly below -maxtxfee (~0.05280000):
- res = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 0.1})
- assert_greater_than(res["fee"], 0.05)
- assert_greater_than(0.06, res["fee"])
+ res = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 0.1, "add_inputs": True})
+ assert_approx(res["fee"], 0.055, 0.005)
# feeRate of 10 BTC / KB produces a total fee well above -maxtxfee
# previously this was silently capped at -maxtxfee
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10})
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10, "add_inputs": True})
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():1}, 0, {"feeRate": 10, "add_inputs": False})
# partially sign multisig things with node 1
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs={self.nodes[1].getnewaddress():29.99}, options={'changeAddress': self.nodes[1].getrawchangeaddress()})['psbt']
@@ -239,7 +254,7 @@ class PSBTTest(BitcoinTestFramework):
# replaceable arg
block_height = self.nodes[0].getblockcount()
unspent = self.nodes[0].listunspent()[0]
- psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False}, False)
+ psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False, "add_inputs": True}, False)
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
@@ -247,7 +262,7 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2)
# Same construction with only locktime set and RBF explicitly enabled
- psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True}, True)
+ psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True, "add_inputs": True}, True)
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
@@ -255,7 +270,7 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(decoded_psbt["tx"]["locktime"], block_height)
# Same construction without optional arguments
- psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
+ psbtx_info = self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
@@ -264,7 +279,7 @@ class PSBTTest(BitcoinTestFramework):
# Same construction without optional arguments, for a node with -walletrbf=0
unspent1 = self.nodes[1].listunspent()[0]
- psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height)
+ psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height, {"add_inputs": True})
decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
@@ -275,7 +290,7 @@ class PSBTTest(BitcoinTestFramework):
self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False)
# Regression test for 14473 (mishandling of already-signed witness transaction):
- psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
+ psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], 0, {"add_inputs": True})
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"])
assert_equal(complete_psbt, double_processed_psbt)
@@ -344,7 +359,8 @@ class PSBTTest(BitcoinTestFramework):
for i, signer in enumerate(signers):
self.nodes[2].unloadwallet("wallet{}".format(i))
- self.test_utxo_conversion()
+ # TODO: Re-enable this for segwit v1
+ # self.test_utxo_conversion()
# Test that psbts with p2pkh outputs are created properly
p2pkh = self.nodes[0].getnewaddress(address_type='legacy')
@@ -467,7 +483,7 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(analysis['next'], 'creator')
assert_equal(analysis['error'], 'PSBT is not valid. Input 0 specifies invalid prevout')
- assert_raises_rpc_error(-25, 'Missing inputs', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
+ assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 14cad3d1b8..23b5e647d6 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -424,11 +424,12 @@ class RawTransactionsTest(BitcoinTestFramework):
####################################
# Test the minimum transaction version number that fits in a signed 32-bit integer.
+ # As transaction version is unsigned, this should convert to its unsigned equivalent.
tx = CTransaction()
tx.nVersion = -0x80000000
rawtx = ToHex(tx)
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
- assert_equal(decrawtx['version'], -0x80000000)
+ assert_equal(decrawtx['version'], 0x80000000)
# Test the maximum transaction version number that fits in a signed 32-bit integer.
tx = CTransaction()
diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py
index 05308931e3..81eb881234 100644
--- a/test/functional/test_framework/authproxy.py
+++ b/test/functional/test_framework/authproxy.py
@@ -115,6 +115,8 @@ class AuthServiceProxy():
except OSError as e:
retry = (
'[WinError 10053] An established connection was aborted by the software in your host machine' in str(e))
+ # Workaround for a bug on macOS. See https://bugs.python.org/issue33450
+ retry = retry or ('[Errno 41] Protocol wrong type for socket' in str(e))
if retry:
self.__conn.close()
self.__conn.request(method, path, postdata, headers)
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 4d1dd4422e..2462a9a6db 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -31,7 +31,7 @@ from test_framework.siphash import siphash256
from test_framework.util import hex_str_to_bytes, assert_equal
MIN_VERSION_SUPPORTED = 60001
-MY_VERSION = 70014 # past bip-31 for ping/pong
+MY_VERSION = 70016 # past wtxid relay
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37)
@@ -45,6 +45,10 @@ MAX_MONEY = 21000000 * COIN
BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out
+MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages
+MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result
+MAX_INV_SIZE = 50000 # Maximum number of entries in an 'inv' protocol message
+
NODE_NETWORK = (1 << 0)
NODE_GETUTXO = (1 << 1)
NODE_BLOOM = (1 << 2)
@@ -55,6 +59,7 @@ MSG_TX = 1
MSG_BLOCK = 2
MSG_FILTERED_BLOCK = 3
MSG_CMPCT_BLOCK = 4
+MSG_WTX = 5
MSG_WITNESS_FLAG = 1 << 30
MSG_TYPE_MASK = 0xffffffff >> 2
@@ -203,17 +208,19 @@ class CAddress:
self.ip = "0.0.0.0"
self.port = 0
- def deserialize(self, f, with_time=True):
+ def deserialize(self, f, *, with_time=True):
if with_time:
+ # VERSION messages serialize CAddress objects without time
self.time = struct.unpack("<i", f.read(4))[0]
self.nServices = struct.unpack("<Q", f.read(8))[0]
self.pchReserved = f.read(12)
self.ip = socket.inet_ntoa(f.read(4))
self.port = struct.unpack(">H", f.read(2))[0]
- def serialize(self, with_time=True):
+ def serialize(self, *, with_time=True):
r = b""
if with_time:
+ # VERSION messages serialize CAddress objects without time
r += struct.pack("<i", self.time)
r += struct.pack("<Q", self.nServices)
r += self.pchReserved
@@ -236,7 +243,8 @@ class CInv:
MSG_TX | MSG_WITNESS_FLAG: "WitnessTx",
MSG_BLOCK | MSG_WITNESS_FLAG: "WitnessBlock",
MSG_FILTERED_BLOCK: "filtered Block",
- 4: "CompactBlock"
+ 4: "CompactBlock",
+ 5: "WTX",
}
def __init__(self, t=0, h=0):
@@ -257,6 +265,9 @@ class CInv:
return "CInv(type=%s hash=%064x)" \
% (self.typemap[self.type], self.hash)
+ def __eq__(self, other):
+ return isinstance(other, CInv) and self.hash == other.hash and self.type == other.type
+
class CBlockLocator:
__slots__ = ("nVersion", "vHave")
@@ -969,10 +980,10 @@ class msg_version:
self.nServices = struct.unpack("<Q", f.read(8))[0]
self.nTime = struct.unpack("<q", f.read(8))[0]
self.addrTo = CAddress()
- self.addrTo.deserialize(f, False)
+ self.addrTo.deserialize(f, with_time=False)
self.addrFrom = CAddress()
- self.addrFrom.deserialize(f, False)
+ self.addrFrom.deserialize(f, with_time=False)
self.nNonce = struct.unpack("<Q", f.read(8))[0]
self.strSubVer = deser_string(f)
@@ -992,8 +1003,8 @@ class msg_version:
r += struct.pack("<i", self.nVersion)
r += struct.pack("<Q", self.nServices)
r += struct.pack("<q", self.nTime)
- r += self.addrTo.serialize(False)
- r += self.addrFrom.serialize(False)
+ r += self.addrTo.serialize(with_time=False)
+ r += self.addrFrom.serialize(with_time=False)
r += struct.pack("<Q", self.nNonce)
r += ser_string(self.strSubVer)
r += struct.pack("<i", self.nStartingHeight)
@@ -1118,6 +1129,22 @@ class msg_tx:
def __repr__(self):
return "msg_tx(tx=%s)" % (repr(self.tx))
+class msg_wtxidrelay:
+ __slots__ = ()
+ msgtype = b"wtxidrelay"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_wtxidrelay()"
+
class msg_no_witness_tx(msg_tx):
__slots__ = ()
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index 45063aaff2..07811667a8 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -26,6 +26,7 @@ import threading
from test_framework.messages import (
CBlockHeader,
+ MAX_HEADERS_RESULTS,
MIN_VERSION_SUPPORTED,
msg_addr,
msg_block,
@@ -58,6 +59,8 @@ from test_framework.messages import (
MSG_TYPE_MASK,
msg_verack,
msg_version,
+ MSG_WTX,
+ msg_wtxidrelay,
NODE_NETWORK,
NODE_WITNESS,
sha256,
@@ -95,6 +98,7 @@ MESSAGEMAP = {
b"tx": msg_tx,
b"verack": msg_verack,
b"version": msg_version,
+ b"wtxidrelay": msg_wtxidrelay,
}
MAGIC_BYTES = {
@@ -279,9 +283,13 @@ class P2PInterface(P2PConnection):
def __init__(self):
super().__init__()
- # Track number of messages of each type received and the most recent
- # message of each type
+ # Track number of messages of each type received.
+ # Should be read-only in a test.
self.message_count = defaultdict(int)
+
+ # Track the most recent message of each type.
+ # To wait for a message to be received, pop that message from
+ # this and use wait_until.
self.last_message = {}
# A count of the number of ping messages we've sent to the node
@@ -355,6 +363,7 @@ class P2PInterface(P2PConnection):
def on_sendcmpct(self, message): pass
def on_sendheaders(self, message): pass
def on_tx(self, message): pass
+ def on_wtxidrelay(self, message): pass
def on_inv(self, message):
want = msg_getdata()
@@ -372,6 +381,8 @@ class P2PInterface(P2PConnection):
def on_version(self, message):
assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED)
+ if message.nVersion >= 70016:
+ self.send_message(msg_wtxidrelay())
self.send_message(msg_verack())
self.nServices = message.nServices
@@ -465,7 +476,7 @@ class P2PInterface(P2PConnection):
def wait_for_verack(self, timeout=60):
def test_function():
- return self.message_count["verack"]
+ return "verack" in self.last_message
self.wait_until(test_function, timeout=timeout)
@@ -492,7 +503,7 @@ class P2PInterface(P2PConnection):
# P2PConnection acquires this lock whenever delivering a message to a P2PInterface.
# This lock should be acquired in the thread running the test logic to synchronize
# access to any data shared with the P2PInterface or P2PConnection.
-mininode_lock = threading.RLock()
+mininode_lock = threading.Lock()
class NetworkThread(threading.Thread):
@@ -553,7 +564,6 @@ class P2PDataStore(P2PInterface):
return
headers_list = [self.block_store[self.last_block_hash]]
- maxheaders = 2000
while headers_list[-1].sha256 not in locator.vHave:
# Walk back through the block store, adding headers to headers_list
# as we go.
@@ -569,7 +579,7 @@ class P2PDataStore(P2PInterface):
break
# Truncate the list if there are too many headers
- headers_list = headers_list[:-maxheaders - 1:-1]
+ headers_list = headers_list[:-MAX_HEADERS_RESULTS - 1:-1]
response = msg_headers(headers_list)
if response is not None:
@@ -654,12 +664,10 @@ class P2PTxInvStore(P2PInterface):
super().on_inv(message) # Send getdata in response.
# Store how many times invs have been received for each tx.
for i in message.inv:
- if i.type == MSG_TX:
+ if (i.type == MSG_TX) or (i.type == MSG_WTX):
# save txid
self.tx_invs_received[i.hash] += 1
- super().on_inv(message)
-
def get_invs(self):
with mininode_lock:
return list(self.tx_invs_received.keys())
@@ -669,6 +677,6 @@ class P2PTxInvStore(P2PInterface):
The mempool should mark unbroadcast=False for these transactions.
"""
# Wait until invs have been received (and getdatas sent) for each txid.
- self.wait_until(lambda: set(self.get_invs()) == set([int(tx, 16) for tx in txns]), timeout)
+ self.wait_until(lambda: set(self.tx_invs_received.keys()) == set([int(tx, 16) for tx in txns]), timeout)
# Flush messages and wait for the getdatas to be processed
self.sync_with_ping()
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 9f5e9e5f0d..8d402d4888 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -31,8 +31,7 @@ from .util import (
disconnect_nodes,
get_datadir_path,
initialize_datadir,
- sync_blocks,
- sync_mempools,
+ wait_until,
)
@@ -355,9 +354,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# See fPreferredDownload in net_processing.
#
# If further outbound connections are needed, they can be added at the beginning of the test with e.g.
- # connect_nodes(self.nodes[1], 2)
+ # self.connect_nodes(1, 2)
for i in range(self.num_nodes - 1):
- connect_nodes(self.nodes[i + 1], i)
+ self.connect_nodes(i + 1, i)
self.sync_all()
def setup_nodes(self):
@@ -452,7 +451,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert_equal(len(binary), num_nodes)
assert_equal(len(binary_cli), num_nodes)
for i in range(num_nodes):
- self.nodes.append(TestNode(
+ test_node_i = TestNode(
i,
get_datadir_path(self.options.tmpdir, i),
chain=self.chain,
@@ -470,7 +469,15 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
start_perf=self.options.perf,
use_valgrind=self.options.valgrind,
descriptors=self.options.descriptors,
- ))
+ )
+ self.nodes.append(test_node_i)
+ if not test_node_i.version_is_at_least(170000):
+ # adjust conf for pre 17
+ conf_file = test_node_i.bitcoinconf
+ with open(conf_file, 'r', encoding='utf8') as conf:
+ conf_data = conf.read()
+ with open(conf_file, 'w', encoding='utf8') as conf:
+ conf.write(conf_data.replace('[regtest]', ''))
def start_node(self, i, *args, **kwargs):
"""Start a bitcoind"""
@@ -526,11 +533,17 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def wait_for_node_exit(self, i, timeout):
self.nodes[i].process.wait(timeout)
+ def connect_nodes(self, a, b):
+ connect_nodes(self.nodes[a], b)
+
+ def disconnect_nodes(self, a, b):
+ disconnect_nodes(self.nodes[a], b)
+
def split_network(self):
"""
Split the network of four nodes into nodes 0/1 and 2/3.
"""
- disconnect_nodes(self.nodes[1], 2)
+ self.disconnect_nodes(1, 2)
self.sync_all(self.nodes[:2])
self.sync_all(self.nodes[2:])
@@ -538,18 +551,60 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"""
Join the (previously split) network halves together.
"""
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.sync_all()
- def sync_blocks(self, nodes=None, **kwargs):
- sync_blocks(nodes or self.nodes, **kwargs)
-
- def sync_mempools(self, nodes=None, **kwargs):
- sync_mempools(nodes or self.nodes, **kwargs)
-
- def sync_all(self, nodes=None, **kwargs):
- self.sync_blocks(nodes, **kwargs)
- self.sync_mempools(nodes, **kwargs)
+ def sync_blocks(self, nodes=None, wait=1, timeout=60):
+ """
+ Wait until everybody has the same tip.
+ sync_blocks needs to be called with an rpc_connections set that has least
+ one node already synced to the latest, stable tip, otherwise there's a
+ chance it might return before all nodes are stably synced.
+ """
+ rpc_connections = nodes or self.nodes
+ timeout = int(timeout * self.options.timeout_factor)
+ stop_time = time.time() + timeout
+ while time.time() <= stop_time:
+ best_hash = [x.getbestblockhash() for x in rpc_connections]
+ if best_hash.count(best_hash[0]) == len(rpc_connections):
+ return
+ # Check that each peer has at least one connection
+ assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
+ time.sleep(wait)
+ raise AssertionError("Block sync timed out after {}s:{}".format(
+ timeout,
+ "".join("\n {!r}".format(b) for b in best_hash),
+ ))
+
+ def sync_mempools(self, nodes=None, wait=1, timeout=60, flush_scheduler=True):
+ """
+ Wait until everybody has the same transactions in their memory
+ pools
+ """
+ rpc_connections = nodes or self.nodes
+ timeout = int(timeout * self.options.timeout_factor)
+ stop_time = time.time() + timeout
+ while time.time() <= stop_time:
+ pool = [set(r.getrawmempool()) for r in rpc_connections]
+ if pool.count(pool[0]) == len(rpc_connections):
+ if flush_scheduler:
+ for r in rpc_connections:
+ r.syncwithvalidationinterfacequeue()
+ return
+ # Check that each peer has at least one connection
+ assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
+ time.sleep(wait)
+ raise AssertionError("Mempool sync timed out after {}s:{}".format(
+ timeout,
+ "".join("\n {!r}".format(m) for m in pool),
+ ))
+
+ def sync_all(self, nodes=None):
+ self.sync_blocks(nodes)
+ self.sync_mempools(nodes)
+
+ def wait_until(self, test_function, timeout=60, lock=None):
+ return wait_until(test_function, timeout=timeout, lock=lock, timeout_factor=self.options.timeout_factor)
# Private helper methods. These should not be accessed by the subclass test scripts.
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index ebc0501e11..66bb2c89b5 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -23,6 +23,7 @@ import sys
from .authproxy import JSONRPCException
from .descriptors import descsum_create
+from .messages import MY_SUBVERSION
from .util import (
MAX_NODES,
append_config,
@@ -549,11 +550,16 @@ class TestNode():
assert self.p2ps, self._node_msg("No p2p connection")
return self.p2ps[0]
+ def num_connected_mininodes(self):
+ """Return number of test framework p2p connections to the node."""
+ return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION])
+
def disconnect_p2ps(self):
"""Close all p2p connections to the node."""
for p in self.p2ps:
p.peer_disconnect()
del self.p2ps[:]
+ wait_until(lambda: self.num_connected_mininodes() == 0)
class TestNodeCLIAttr:
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 52306c8c3d..506057f1fa 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -25,6 +25,7 @@ logger = logging.getLogger("TestFramework.utils")
# Assert functions
##################
+
def assert_approx(v, vexp, vspan=0.00001):
"""Assert that `v` is within `vspan` of `vexp`"""
if v < vexp - vspan:
@@ -32,6 +33,7 @@ def assert_approx(v, vexp, vspan=0.00001):
if v > vexp + vspan:
raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
+
def assert_fee_amount(fee, tx_size, fee_per_kB):
"""Assert the fee was in range"""
target_fee = round(tx_size * fee_per_kB / 1000, 8)
@@ -41,21 +43,26 @@ def assert_fee_amount(fee, tx_size, fee_per_kB):
if fee > (tx_size + 2) * fee_per_kB / 1000:
raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee)))
+
def assert_equal(thing1, thing2, *args):
if thing1 != thing2 or any(thing1 != arg for arg in args):
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
+
def assert_greater_than(thing1, thing2):
if thing1 <= thing2:
raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))
+
def assert_greater_than_or_equal(thing1, thing2):
if thing1 < thing2:
raise AssertionError("%s < %s" % (str(thing1), str(thing2)))
+
def assert_raises(exc, fun, *args, **kwds):
assert_raises_message(exc, None, fun, *args, **kwds)
+
def assert_raises_message(exc, message, fun, *args, **kwds):
try:
fun(*args, **kwds)
@@ -71,6 +78,7 @@ def assert_raises_message(exc, message, fun, *args, **kwds):
else:
raise AssertionError("No exception raised")
+
def assert_raises_process_error(returncode, output, fun, *args, **kwds):
"""Execute a process and asserts the process return code and output.
@@ -95,6 +103,7 @@ def assert_raises_process_error(returncode, output, fun, *args, **kwds):
else:
raise AssertionError("No exception raised")
+
def assert_raises_rpc_error(code, message, fun, *args, **kwds):
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
@@ -113,6 +122,7 @@ def assert_raises_rpc_error(code, message, fun, *args, **kwds):
"""
assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
+
def try_rpc(code, message, fun, *args, **kwds):
"""Tries to run an rpc command.
@@ -134,22 +144,22 @@ def try_rpc(code, message, fun, *args, **kwds):
else:
return False
+
def assert_is_hex_string(string):
try:
int(string, 16)
except Exception as e:
- raise AssertionError(
- "Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
+ raise AssertionError("Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
+
def assert_is_hash_string(string, length=64):
if not isinstance(string, str):
raise AssertionError("Expected a string, got type %r" % type(string))
elif length and len(string) != length:
- raise AssertionError(
- "String of length %d expected; got %d" % (length, len(string)))
+ raise AssertionError("String of length %d expected; got %d" % (length, len(string)))
elif not re.match('[abcdef0-9]+$', string):
- raise AssertionError(
- "String %r contains invalid characters for a hash." % string)
+ raise AssertionError("String %r contains invalid characters for a hash." % string)
+
def assert_array_result(object_array, to_match, expected, should_not_find=False):
"""
@@ -180,9 +190,11 @@ def assert_array_result(object_array, to_match, expected, should_not_find=False)
if num_matched > 0 and should_not_find:
raise AssertionError("Objects were found %s" % (str(to_match)))
+
# Utility functions
###################
+
def check_json_precision():
"""Make sure json library being used does not lose precision converting BTC values"""
n = Decimal("20000000.00000003")
@@ -190,11 +202,13 @@ def check_json_precision():
if satoshis != 2000000000000003:
raise RuntimeError("JSON encode/decode loses precision")
+
def EncodeDecimal(o):
if isinstance(o, Decimal):
return str(o)
raise TypeError(repr(o) + " is not JSON serializable")
+
def count_bytes(hex_string):
return len(bytearray.fromhex(hex_string))
@@ -202,12 +216,15 @@ def count_bytes(hex_string):
def hex_str_to_bytes(hex_str):
return unhexlify(hex_str.encode('ascii'))
+
def str_to_b64str(string):
return b64encode(string.encode('utf-8')).decode('ascii')
+
def satoshi_round(amount):
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
+
def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
if attempts == float('inf') and timeout == float('inf'):
timeout = 60
@@ -235,6 +252,7 @@ def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=N
raise AssertionError("Predicate {} not true after {} seconds".format(predicate_source, timeout))
raise RuntimeError('Unreachable')
+
# RPC/P2P connection constants and functions
############################################
@@ -250,6 +268,7 @@ class PortSeed:
# Must be initialized with a unique integer for each process
n = None
+
def get_rpc_proxy(url, node_number, *, timeout=None, coveragedir=None):
"""
Args:
@@ -271,18 +290,20 @@ def get_rpc_proxy(url, node_number, *, timeout=None, coveragedir=None):
proxy = AuthServiceProxy(url, **proxy_kwargs)
proxy.url = url # store URL on proxy for info
- coverage_logfile = coverage.get_filename(
- coveragedir, node_number) if coveragedir else None
+ coverage_logfile = coverage.get_filename(coveragedir, node_number) if coveragedir else None
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
+
def p2p_port(n):
assert n <= MAX_NODES
return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
+
def rpc_port(n):
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
+
def rpc_url(datadir, i, chain, rpchost):
rpc_u, rpc_p = get_auth_cookie(datadir, chain)
host = '127.0.0.1'
@@ -295,9 +316,11 @@ def rpc_url(datadir, i, chain, rpchost):
host = rpchost
return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port))
+
# Node functions
################
+
def initialize_datadir(dirname, n, chain):
datadir = get_datadir_path(dirname, n)
if not os.path.isdir(datadir):
@@ -327,21 +350,17 @@ def initialize_datadir(dirname, n, chain):
os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
return datadir
-def adjust_bitcoin_conf_for_pre_17(conf_file):
- with open(conf_file,'r', encoding='utf8') as conf:
- conf_data = conf.read()
- with open(conf_file, 'w', encoding='utf8') as conf:
- conf_data_changed = conf_data.replace('[regtest]', '')
- conf.write(conf_data_changed)
def get_datadir_path(dirname, n):
return os.path.join(dirname, "node" + str(n))
+
def append_config(datadir, options):
with open(os.path.join(datadir, "bitcoin.conf"), 'a', encoding='utf8') as f:
for option in options:
f.write(option + "\n")
+
def get_auth_cookie(datadir, chain):
user = None
password = None
@@ -366,20 +385,24 @@ def get_auth_cookie(datadir, chain):
raise ValueError("No RPC credentials")
return user, password
+
# If a cookie file exists in the given datadir, delete it.
def delete_cookie_file(datadir, chain):
if os.path.isfile(os.path.join(datadir, chain, ".cookie")):
logger.debug("Deleting leftover cookie file")
os.remove(os.path.join(datadir, chain, ".cookie"))
+
def softfork_active(node, key):
"""Return whether a softfork is active."""
return node.getblockchaininfo()['softforks'][key]['active']
+
def set_node_times(nodes, t):
for node in nodes:
node.setmocktime(t)
+
def disconnect_nodes(from_connection, node_num):
def get_peer_ids():
result = []
@@ -392,7 +415,7 @@ def disconnect_nodes(from_connection, node_num):
if not peer_ids:
logger.warning("disconnect_nodes: {} and {} were not connected".format(
from_connection.index,
- node_num
+ node_num,
))
return
for peer_id in peer_ids:
@@ -402,12 +425,13 @@ def disconnect_nodes(from_connection, node_num):
# If this node is disconnected between calculating the peer id
# and issuing the disconnect, don't worry about it.
# This avoids a race condition if we're mass-disconnecting peers.
- if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
+ if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
raise
# wait to disconnect
wait_until(lambda: not get_peer_ids(), timeout=5)
+
def connect_nodes(from_connection, node_num):
ip_port = "127.0.0.1:" + str(p2p_port(node_num))
from_connection.addnode(ip_port, "onetry")
@@ -420,50 +444,6 @@ def connect_nodes(from_connection, node_num):
wait_until(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
-def sync_blocks(rpc_connections, *, wait=1, timeout=60):
- """
- Wait until everybody has the same tip.
-
- sync_blocks needs to be called with an rpc_connections set that has least
- one node already synced to the latest, stable tip, otherwise there's a
- chance it might return before all nodes are stably synced.
- """
- stop_time = time.time() + timeout
- while time.time() <= stop_time:
- best_hash = [x.getbestblockhash() for x in rpc_connections]
- if best_hash.count(best_hash[0]) == len(rpc_connections):
- return
- # Check that each peer has at least one connection
- assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
- time.sleep(wait)
- raise AssertionError("Block sync timed out after {}s:{}".format(
- timeout,
- "".join("\n {!r}".format(b) for b in best_hash),
- ))
-
-
-def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True):
- """
- Wait until everybody has the same transactions in their memory
- pools
- """
- stop_time = time.time() + timeout
- while time.time() <= stop_time:
- pool = [set(r.getrawmempool()) for r in rpc_connections]
- if pool.count(pool[0]) == len(rpc_connections):
- if flush_scheduler:
- for r in rpc_connections:
- r.syncwithvalidationinterfacequeue()
- return
- # Check that each peer has at least one connection
- assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
- time.sleep(wait)
- raise AssertionError("Mempool sync timed out after {}s:{}".format(
- timeout,
- "".join("\n {!r}".format(m) for m in pool),
- ))
-
-
# Transaction/Block functions
#############################
@@ -479,6 +459,7 @@ def find_output(node, txid, amount, *, blockhash=None):
return i
raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
+
def gather_inputs(from_node, amount_needed, confirmations_required=1):
"""
Return a random set of unspent txouts that are enough to pay amount_needed
@@ -496,6 +477,7 @@ def gather_inputs(from_node, amount_needed, confirmations_required=1):
raise RuntimeError("Insufficient funds: need %d, have %d" % (amount_needed, total_in))
return (total_in, inputs)
+
def make_change(from_node, amount_in, amount_out, fee):
"""
Create change output(s), return them
@@ -513,6 +495,7 @@ def make_change(from_node, amount_in, amount_out, fee):
outputs[from_node.getnewaddress()] = change
return outputs
+
def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
"""
Create a random transaction.
@@ -532,6 +515,7 @@ def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
return (txid, signresult["hex"], fee)
+
# Helper to create at least "count" utxos
# Pass in a fee that is sufficient for relay and mining new transactions.
def create_confirmed_utxos(fee, node, count):
@@ -564,6 +548,7 @@ def create_confirmed_utxos(fee, node, count):
assert len(utxos) >= count
return utxos
+
# Create large OP_RETURN txouts that can be appended to a transaction
# to make it large (helper for constructing large transactions).
def gen_return_txouts():
@@ -583,6 +568,7 @@ def gen_return_txouts():
txouts.append(txout)
return txouts
+
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
# transaction to make it large. See gen_return_txouts() above.
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
@@ -606,6 +592,7 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
txids.append(txid)
return txids
+
def mine_large_block(node, utxos=None):
# generate a 66k transaction,
# and 14 of them is close to the 1MB block limit
@@ -619,6 +606,7 @@ def mine_large_block(node, utxos=None):
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
node.generate(1)
+
def find_vout_for_address(node, txid, addr):
"""
Locate the vout index of the given transaction sending to the
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index c0bc471d5a..95c2b7c5ec 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -190,6 +190,7 @@ BASE_SCRIPTS = [
'rpc_preciousblock.py',
'wallet_importprunedfunds.py',
'p2p_leak_tx.py',
+ 'p2p_eviction.py',
'rpc_signmessage.py',
'rpc_generateblock.py',
'wallet_balance.py',
@@ -232,20 +233,23 @@ BASE_SCRIPTS = [
'feature_includeconf.py',
'feature_asmap.py',
'mempool_unbroadcast.py',
+ 'mempool_compatibility.py',
'rpc_deriveaddresses.py',
'rpc_deriveaddresses.py --usecli',
+ 'p2p_ping.py',
'rpc_scantxoutset.py',
'feature_logging.py',
'p2p_node_network_limited.py',
'p2p_permissions.py',
'feature_blocksdir.py',
'feature_config_args.py',
- 'rpc_getaddressinfo_labels_purpose_deprecation.py',
- 'rpc_getaddressinfo_label_deprecation.py',
+ 'feature_settings.py',
'rpc_getdescriptorinfo.py',
+ 'rpc_getpeerinfo_banscore_deprecation.py',
'rpc_help.py',
'feature_help.py',
'feature_shutdown.py',
+ 'p2p_ibd_txrelay.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]
@@ -396,11 +400,12 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
args = args or []
# Warn if bitcoind is already running
- # pidof might fail or return an empty string if bitcoind is not running
try:
- if subprocess.check_output(["pidof", "bitcoind"]) not in [b'']:
+ # pgrep exits with code zero when one or more matching processes found
+ if subprocess.run(["pgrep", "-x", "bitcoind"], stdout=subprocess.DEVNULL).returncode == 0:
print("%sWARNING!%s There is already a bitcoind process running on this system. Tests may fail unexpectedly due to resource contention!" % (BOLD[1], BOLD[0]))
- except (OSError, subprocess.SubprocessError):
+ except OSError:
+ # pgrep not supported
pass
# Warn if there is a cache directory
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index 524e1593ba..18f0beb598 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -71,8 +71,7 @@ class ToolWalletTest(BitcoinTestFramework):
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 parameter -foo', '-foo')
self.assert_raises_tool_error(
- 'Error initializing wallet database environment "{}"!\nError loading wallet.dat. Is wallet being used by other process?'
- .format(os.path.join(self.nodes[0].datadir, self.chain, 'wallets')),
+ 'Error loading wallet.dat. Is wallet being used by another process?',
'-wallet=wallet.dat',
'info',
)
diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py
index 90d17a806c..8837e13005 100755
--- a/test/functional/wallet_abandonconflict.py
+++ b/test/functional/wallet_abandonconflict.py
@@ -95,8 +95,7 @@ class AbandonConflictTest(BitcoinTestFramework):
# Restart the node with a higher min relay fee so the parent tx is no longer in mempool
# TODO: redo with eviction
- self.stop_node(0)
- self.start_node(0, extra_args=["-minrelaytxfee=0.0001"])
+ self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
assert self.nodes[0].getmempoolinfo()['loaded']
# Verify txs no longer in either node's mempool
@@ -123,8 +122,7 @@ class AbandonConflictTest(BitcoinTestFramework):
balance = newbalance
# Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned
- self.stop_node(0)
- self.start_node(0, extra_args=["-minrelaytxfee=0.00001"])
+ self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"])
assert self.nodes[0].getmempoolinfo()['loaded']
assert_equal(len(self.nodes[0].getrawmempool()), 0)
@@ -145,8 +143,7 @@ class AbandonConflictTest(BitcoinTestFramework):
balance = newbalance
# Remove using high relay fee again
- self.stop_node(0)
- self.start_node(0, extra_args=["-minrelaytxfee=0.0001"])
+ self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
assert self.nodes[0].getmempoolinfo()['loaded']
assert_equal(len(self.nodes[0].getrawmempool()), 0)
newbalance = self.nodes[0].getbalance()
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index 780cce9d02..eddd938847 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -110,9 +110,7 @@ class AvoidReuseTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
- # Stop and restart node 1
- self.stop_node(1)
- self.start_node(1)
+ self.restart_node(1)
connect_nodes(self.nodes[0], 1)
# Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 8efa66a856..31829a18b3 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -12,7 +12,6 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
connect_nodes,
- sync_blocks,
)
@@ -264,7 +263,7 @@ class WalletTest(BitcoinTestFramework):
# Now confirm tx_orig
self.restart_node(1, ['-persistmempool=0'])
connect_nodes(self.nodes[0], 1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
self.nodes[1].sendrawtransaction(tx_orig)
self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)
self.sync_all()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 797c903dd3..81382d94ad 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -119,7 +119,7 @@ class WalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
self.nodes[2].lockunspent(False, [unspent_0])
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
+ assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
assert_equal([unspent_0], self.nodes[2].listlockunspent())
self.nodes[2].lockunspent(True, [unspent_0])
assert_equal(len(self.nodes[2].listlockunspent()), 0)
@@ -219,6 +219,60 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ # Sendmany with explicit fee (BTC/kB)
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ estimate_mode='bTc/kB')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ conf_target=-1,
+ estimate_mode='bTc/kB')
+ fee_per_kb = 0.0002500
+ explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
+ txid = self.nodes[2].sendmany(
+ amounts={ address: 10 },
+ conf_target=fee_per_kb,
+ estimate_mode='bTc/kB',
+ )
+ self.nodes[2].generate(1)
+ self.sync_all(self.nodes[0:3])
+ node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ assert_equal(self.nodes[2].getbalance(), node_2_bal)
+ node_0_bal += Decimal('10')
+ assert_equal(self.nodes[0].getbalance(), node_0_bal)
+
+ # Sendmany with explicit fee (SAT/B)
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ estimate_mode='sat/b')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ conf_target=-1,
+ estimate_mode='sat/b')
+ fee_sat_per_b = 2
+ fee_per_kb = fee_sat_per_b / 100000.0
+ explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
+ txid = self.nodes[2].sendmany(
+ amounts={ address: 10 },
+ conf_target=fee_sat_per_b,
+ estimate_mode='sAT/b',
+ )
+ self.nodes[2].generate(1)
+ self.sync_all(self.nodes[0:3])
+ balance = self.nodes[2].getbalance()
+ node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ assert_equal(balance, node_2_bal)
+ node_0_bal += Decimal('10')
+ assert_equal(self.nodes[0].getbalance(), node_0_bal)
+
self.start_node(3, self.nodes[3].extra_args)
connect_nodes(self.nodes[0], 3)
self.sync_all()
@@ -309,6 +363,9 @@ class WalletTest(BitcoinTestFramework):
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
# General checks for errors from incorrect inputs
+ # This will raise an exception because the amount is negative
+ assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
+
# This will raise an exception because the amount type is wrong
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
@@ -349,6 +406,74 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
+ # send with explicit btc/kb fee
+ self.log.info("test explicit fee (sendtoaddress as btc/kb)")
+ self.nodes[0].generate(1)
+ self.sync_all(self.nodes[0:3])
+ prebalance = self.nodes[2].getbalance()
+ assert prebalance > 2
+ address = self.nodes[1].getnewaddress()
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ estimate_mode='BTc/Kb')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ conf_target=-1,
+ estimate_mode='btc/kb')
+ txid = self.nodes[2].sendtoaddress(
+ address=address,
+ amount=1.0,
+ conf_target=0.00002500,
+ estimate_mode='btc/kb',
+ )
+ tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
+ self.sync_all(self.nodes[0:3])
+ self.nodes[0].generate(1)
+ self.sync_all(self.nodes[0:3])
+ postbalance = self.nodes[2].getbalance()
+ fee = prebalance - postbalance - Decimal('1')
+ assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
+
+ # send with explicit sat/b fee
+ self.sync_all(self.nodes[0:3])
+ self.log.info("test explicit fee (sendtoaddress as sat/b)")
+ self.nodes[0].generate(1)
+ prebalance = self.nodes[2].getbalance()
+ assert prebalance > 2
+ address = self.nodes[1].getnewaddress()
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ estimate_mode='SAT/b')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ conf_target=-1,
+ estimate_mode='SAT/b')
+ txid = self.nodes[2].sendtoaddress(
+ address=address,
+ amount=1.0,
+ conf_target=2,
+ estimate_mode='SAT/B',
+ )
+ tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
+ self.sync_all(self.nodes[0:3])
+ self.nodes[0].generate(1)
+ self.sync_all(self.nodes[0:3])
+ postbalance = self.nodes[2].getbalance()
+ fee = prebalance - postbalance - Decimal('1')
+ assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
+
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
@@ -468,7 +593,7 @@ class WalletTest(BitcoinTestFramework):
node0_balance = self.nodes[0].getbalance()
# With walletrejectlongchains we will not create the tx and store it in our wallet.
- assert_raises_rpc_error(-4, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
+ assert_raises_rpc_error(-6, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
# Verify nothing new in wallet
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 27197e3b6d..72c85b8832 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -71,6 +71,7 @@ class BumpFeeTest(BitcoinTestFramework):
self.log.info("Running tests")
dest_address = peer_node.getnewaddress()
+ test_invalid_parameters(rbf_node, dest_address)
test_simple_bumpfee_succeeds(self, "default", rbf_node, peer_node, dest_address)
test_simple_bumpfee_succeeds(self, "fee_rate", rbf_node, peer_node, dest_address)
test_feerate_args(self, rbf_node, peer_node, dest_address)
@@ -92,6 +93,28 @@ class BumpFeeTest(BitcoinTestFramework):
test_small_output_with_feerate_succeeds(self, rbf_node, dest_address)
test_no_more_inputs_fails(self, rbf_node, dest_address)
+def test_invalid_parameters(node, dest_address):
+ txid = spend_one_input(node, dest_address)
+ # invalid estimate mode
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
+ "estimate_mode": "moo",
+ })
+ assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
+ "estimate_mode": 38,
+ })
+ assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
+ "estimate_mode": {
+ "foo": "bar",
+ },
+ })
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
+ "estimate_mode": Decimal("3.141592"),
+ })
+ # confTarget and conf_target
+ assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set", node.bumpfee, txid, {
+ "confTarget": 123,
+ "conf_target": 456,
+ })
def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
self.log.info('Test simple bumpfee: {}'.format(mode))
@@ -127,9 +150,10 @@ def test_feerate_args(self, rbf_node, peer_node, dest_address):
self.sync_mempools((rbf_node, peer_node))
assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
- assert_raises_rpc_error(-8, "confTarget can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1})
+ assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1})
assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL})
+ assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001, "confTarget": 1})
# Bumping to just above minrelay should fail to increase total fee enough, at least
assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py
index cc349c7bc5..6bfb468823 100755
--- a/test/functional/wallet_dump.py
+++ b/test/functional/wallet_dump.py
@@ -190,8 +190,7 @@ class WalletDumpTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "already exists", lambda: self.nodes[0].dumpwallet(wallet_enc_dump))
# Restart node with new wallet, and test importwallet
- self.stop_node(0)
- self.start_node(0, ['-wallet=w2'])
+ self.restart_node(0, ['-wallet=w2'])
# Make sure the address is not IsMine before import
result = self.nodes[0].getaddressinfo(multisig_addr)
@@ -203,5 +202,10 @@ class WalletDumpTest(BitcoinTestFramework):
result = self.nodes[0].getaddressinfo(multisig_addr)
assert result['ismine']
+ self.log.info('Check that wallet is flushed')
+ with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20):
+ self.nodes[0].getnewaddress()
+
+
if __name__ == '__main__':
WalletDumpTest().main()
diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py
index 6cd82ad250..4509c1e0b2 100755
--- a/test/functional/wallet_encryption.py
+++ b/test/functional/wallet_encryption.py
@@ -13,6 +13,7 @@ from test_framework.util import (
assert_greater_than_or_equal,
)
+
class WalletEncryptionTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -72,20 +73,25 @@ class WalletEncryptionTest(BitcoinTestFramework):
# Test timeout bounds
assert_raises_rpc_error(-8, "Timeout cannot be negative.", self.nodes[0].walletpassphrase, passphrase2, -10)
- # Check the timeout
- # Check a time less than the limit
+
+ self.log.info('Check a timeout less than the limit')
MAX_VALUE = 100000000
expected_time = int(time.time()) + MAX_VALUE - 600
self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600)
+ # give buffer for walletpassphrase, since it iterates over all crypted keys
+ expected_time_with_buffer = time.time() + MAX_VALUE - 600
actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
assert_greater_than_or_equal(actual_time, expected_time)
- assert_greater_than(expected_time + 5, actual_time) # 5 second buffer
- # Check a time greater than the limit
+ assert_greater_than(expected_time_with_buffer, actual_time)
+
+ self.log.info('Check a timeout greater than the limit')
expected_time = int(time.time()) + MAX_VALUE - 1
self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000)
+ expected_time_with_buffer = time.time() + MAX_VALUE
actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
assert_greater_than_or_equal(actual_time, expected_time)
- assert_greater_than(expected_time + 5, actual_time) # 5 second buffer
+ assert_greater_than(expected_time_with_buffer, actual_time)
+
if __name__ == '__main__':
WalletEncryptionTest().main()
diff --git a/test/functional/wallet_fallbackfee.py b/test/functional/wallet_fallbackfee.py
index 0c67982bbe..dbf853b35c 100755
--- a/test/functional/wallet_fallbackfee.py
+++ b/test/functional/wallet_fallbackfee.py
@@ -22,7 +22,7 @@ class WalletRBFTest(BitcoinTestFramework):
# test sending a tx with disabled fallback fee (must fail)
self.restart_node(0, extra_args=["-fallbackfee=0"])
- assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
+ assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].fundrawtransaction(self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})))
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1}))
diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py
index c441b75652..3c336623e2 100755
--- a/test/functional/wallet_hd.py
+++ b/test/functional/wallet_hd.py
@@ -103,8 +103,7 @@ class WalletHDTest(BitcoinTestFramework):
self.sync_all()
# Needs rescan
- self.stop_node(1)
- self.start_node(1, extra_args=self.extra_args[1] + ['-rescan'])
+ self.restart_node(1, extra_args=self.extra_args[1] + ['-rescan'])
assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)
# Try a RPC based rescan
@@ -183,8 +182,7 @@ class WalletHDTest(BitcoinTestFramework):
self.nodes[0].generate(10)
# Restart node 1 with keypool of 3 and a different wallet
self.nodes[1].createwallet(wallet_name='origin', blank=True)
- self.stop_node(1)
- self.start_node(1, extra_args=['-keypool=3', '-wallet=origin'])
+ self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin'])
connect_nodes(self.nodes[0], 1)
# sethdseed restoring and seeing txs to addresses out of the keypool
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index fc5d653a91..2d982edef8 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -146,6 +146,14 @@ class ImportDescriptorsTest(BitcoinTestFramework):
ismine=True,
solvable=True)
+ # Check persistence of data and that loading works correctly
+ w1.unloadwallet()
+ self.nodes[1].loadwallet('w1')
+ test_address(w1,
+ key.p2sh_p2wpkh_addr,
+ ismine=True,
+ solvable=True)
+
# # Test importing of a multisig descriptor
key1 = get_generate_key()
key2 = get_generate_key()
@@ -370,6 +378,10 @@ class ImportDescriptorsTest(BitcoinTestFramework):
self.sync_all()
assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
+ # Make sure that descriptor wallets containing multiple xpubs in a single descriptor load correctly
+ wmulti_pub.unloadwallet()
+ self.nodes[1].loadwallet('wmulti_pub')
+
self.log.info("Multisig with distributed keys")
self.nodes[1].createwallet(wallet_name="wmulti_priv1", descriptors=True)
wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index ff9ff34185..a54396cad3 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -7,19 +7,36 @@
Verify that a bitcoind node can load multiple wallet files
"""
from decimal import Decimal
+from threading import Thread
import os
import shutil
import time
+from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.test_node import ErrorMatch
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
+ get_rpc_proxy,
)
FEATURE_LATEST = 169900
+got_loading_error = False
+def test_load_unload(node, name):
+ global got_loading_error
+ for i in range(10):
+ if got_loading_error:
+ return
+ try:
+ node.loadwallet(name)
+ node.unloadwallet(name)
+ except JSONRPCException as e:
+ if e.error['code'] == -4 and 'Wallet already being loading' in e.error['message']:
+ got_loading_error = True
+ return
+
class MultiWalletTest(BitcoinTestFramework):
def set_test_params(self):
@@ -103,7 +120,7 @@ class MultiWalletTest(BitcoinTestFramework):
# should not initialize if one wallet is a copy of another
shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
- exp_stderr = r"BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
+ exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
# should not initialize if wallet file is a symlink
@@ -212,6 +229,18 @@ class MultiWalletTest(BitcoinTestFramework):
w2 = node.get_wallet_rpc(wallet_names[1])
w2.getwalletinfo()
+ self.log.info("Concurrent wallet loading")
+ threads = []
+ for _ in range(3):
+ n = node.cli if self.options.usecli else get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
+ t = Thread(target=test_load_unload, args=(n, wallet_names[2], ))
+ t.start()
+ threads.append(t)
+ for t in threads:
+ t.join()
+ global got_loading_error
+ assert_equal(got_loading_error, True)
+
self.log.info("Load remaining wallets")
for wallet_name in wallet_names[2:]:
loadwallet_name = self.nodes[0].loadwallet(wallet_name)
@@ -229,10 +258,10 @@ class MultiWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Wallet file verification failed. Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
# Fail to load if one wallet is a copy of another
- assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
- assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if wallet file is a symlink
diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py
index 497a5dd95e..455f1fc5e8 100755
--- a/test/functional/wallet_reorgsrestore.py
+++ b/test/functional/wallet_reorgsrestore.py
@@ -77,8 +77,7 @@ class ReorgsRestoreTest(BitcoinTestFramework):
assert_equal(conflicted["walletconflicts"][0], conflicting["txid"])
# Node0 wallet is shutdown
- self.stop_node(0)
- self.start_node(0)
+ self.restart_node(0)
# The block chain re-orgs and the tx is included in a different block
self.nodes[1].generate(9)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index bb81746715..1a76f65215 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -6,7 +6,7 @@
Test upgradewallet RPC. Download node binaries:
-contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+contrib/devtools/previous_release.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
Only v0.15.2 and v0.16.3 are required by this test. The others are used in feature_backwards_compatibility.py
"""
@@ -16,7 +16,6 @@ import shutil
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
- adjust_bitcoin_conf_for_pre_17,
assert_equal,
assert_greater_than,
assert_is_hex_string,
@@ -46,9 +45,6 @@ class UpgradeWalletTest(BitcoinTestFramework):
160300,
150200,
])
- # adapt bitcoin.conf, because older bitcoind's don't recognize config sections
- adjust_bitcoin_conf_for_pre_17(self.nodes[1].bitcoinconf)
- adjust_bitcoin_conf_for_pre_17(self.nodes[2].bitcoinconf)
self.start_nodes()
def dumb_sync_blocks(self):
diff --git a/test/functional/wallet_zapwallettxes.py b/test/functional/wallet_zapwallettxes.py
index adebff360a..7f1cdbd20b 100755
--- a/test/functional/wallet_zapwallettxes.py
+++ b/test/functional/wallet_zapwallettxes.py
@@ -49,17 +49,15 @@ class ZapWalletTXesTest (BitcoinTestFramework):
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
- # Stop-start node0. Both confirmed and unconfirmed transactions remain in the wallet.
- self.stop_node(0)
- self.start_node(0)
+ # Restart node0. Both confirmed and unconfirmed transactions remain in the wallet.
+ self.restart_node(0)
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
- # Stop node0 and restart with zapwallettxes and persistmempool. The unconfirmed
+ # Restart node0 with zapwallettxes and persistmempool. The unconfirmed
# transaction is zapped from the wallet, but is re-added when the mempool is reloaded.
- self.stop_node(0)
- self.start_node(0, ["-persistmempool=1", "-zapwallettxes=2"])
+ self.restart_node(0, ["-persistmempool=1", "-zapwallettxes=2"])
wait_until(lambda: self.nodes[0].getmempoolinfo()['size'] == 1, timeout=3)
self.nodes[0].syncwithvalidationinterfacequeue() # Flush mempool to wallet
@@ -67,10 +65,9 @@ class ZapWalletTXesTest (BitcoinTestFramework):
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
- # Stop node0 and restart with zapwallettxes, but not persistmempool.
+ # Restart node0 with zapwallettxes, but not persistmempool.
# The unconfirmed transaction is zapped and is no longer in the wallet.
- self.stop_node(0)
- self.start_node(0, ["-zapwallettxes=2"])
+ self.restart_node(0, ["-zapwallettxes=2"])
# tx1 is still be available because it was confirmed
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
diff --git a/test/lint/README.md b/test/lint/README.md
index 6b95cc3540..d15c061288 100644
--- a/test/lint/README.md
+++ b/test/lint/README.md
@@ -23,6 +23,12 @@ maintained:
* for `src/crypto/ctaes`: https://github.com/bitcoin-core/ctaes.git (branch master)
* for `src/crc32c`: https://github.com/google/crc32c.git (branch master)
+To do so, add the upstream repository as remote:
+
+```
+git remote add --fetch secp256k1 https://github.com/bitcoin-core/secp256k1.git
+```
+
Usage: `git-subtree-check.sh DIR (COMMIT)`
`COMMIT` may be omitted, in which case `HEAD` is used.
diff --git a/test/lint/git-subtree-check.sh b/test/lint/git-subtree-check.sh
index caa7affc63..5a0500df25 100755
--- a/test/lint/git-subtree-check.sh
+++ b/test/lint/git-subtree-check.sh
@@ -81,7 +81,7 @@ fi
# get the tree in the subtree commit referred to
if [ "d$(git cat-file -t $rev 2>/dev/null)" != dcommit ]; then
- echo "subtree commit $rev unavailable: cannot compare" >&2
+ echo "subtree commit $rev unavailable: cannot compare. Did you add and fetch the remote?" >&2
exit
fi
tree_subtree=$(git show -s --format="%T" $rev)
diff --git a/test/lint/lint-assertions.sh b/test/lint/lint-assertions.sh
index 1aacc09bcc..d30a8ca231 100755
--- a/test/lint/lint-assertions.sh
+++ b/test/lint/lint-assertions.sh
@@ -23,7 +23,7 @@ fi
# Macro CHECK_NONFATAL(condition) should be used instead of assert for RPC code, where it
# is undesirable to crash the whole program. See: src/util/check.h
# src/rpc/server.cpp is excluded from this check since it's mostly meta-code.
-OUTPUT=$(git grep -nE 'assert *\(.*\);' -- "src/rpc/" "src/wallet/rpc*" ":(exclude)src/rpc/server.cpp")
+OUTPUT=$(git grep -nE '\<(A|a)ssert *\(.*\);' -- "src/rpc/" "src/wallet/rpc*" ":(exclude)src/rpc/server.cpp")
if [[ ${OUTPUT} != "" ]]; then
echo "CHECK_NONFATAL(condition) should be used instead of assert for RPC code."
echo
diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh
index 5404565b94..611bd4a8c4 100755
--- a/test/lint/lint-includes.sh
+++ b/test/lint/lint-includes.sh
@@ -64,7 +64,7 @@ EXPECTED_BOOST_INCLUDES=(
boost/preprocessor/cat.hpp
boost/preprocessor/stringize.hpp
boost/signals2/connection.hpp
- boost/signals2/last_value.hpp
+ boost/signals2/optional_last_value.hpp
boost/signals2/signal.hpp
boost/test/unit_test.hpp
boost/thread/condition_variable.hpp
diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh
index e2bb403c4d..2e5b801849 100755
--- a/test/lint/lint-locale-dependence.sh
+++ b/test/lint/lint-locale-dependence.sh
@@ -97,6 +97,7 @@ LOCALE_DEPENDENT_FUNCTIONS=(
snprintf
sprintf
sscanf
+ std::locale::global
std::to_string
stod
stof
diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh
index decea38c4f..72e8ef7c7d 100755
--- a/test/lint/lint-python.sh
+++ b/test/lint/lint-python.sh
@@ -39,7 +39,6 @@ enabled=(
E711 # comparison to None should be 'if cond is None:'
E714 # test for object identity should be "is not"
E721 # do not compare types, use "isinstance()"
- E741 # do not use variables named "l", "O", or "I"
E742 # do not define classes named "l", "O", or "I"
E743 # do not define functions named "l", "O", or "I"
E901 # SyntaxError: invalid syntax
diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh
index 563e076b35..9a26cd9c02 100755
--- a/test/lint/lint-shell.sh
+++ b/test/lint/lint-shell.sh
@@ -25,7 +25,6 @@ disabled=(
disabled_gitian=(
SC2094 # Make sure not to read and write the same file in the same pipeline.
SC2129 # Consider using { cmd1; cmd2; } >> file instead of individual redirects.
- SC2230 # which is non-standard. Use builtin 'command -v' instead.
)
EXIT_CODE=0
diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt
index a7a97eb41f..34f54325b3 100644
--- a/test/lint/lint-spelling.ignore-words.txt
+++ b/test/lint/lint-spelling.ignore-words.txt
@@ -14,3 +14,4 @@ setban
hist
ser
unselect
+lowercased
diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan
index b8fe75c5c5..3d0ac7f995 100644
--- a/test/sanitizer_suppressions/tsan
+++ b/test/sanitizer_suppressions/tsan
@@ -19,8 +19,11 @@ race:CConnman::ThreadMessageHandler
race:fHaveGenesis
race:ProcessNewBlock
race:ThreadImport
+race:LoadWallet
race:WalletBatch::WriteHDChain
+race:BerkeleyBatch
race:BerkeleyDatabase
+race:DatabaseBatch
race:zmq::*
race:bitcoin-qt
# deadlock (TODO fix)