aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md23
-rw-r--r--test/functional/README.md36
-rwxr-xr-xtest/functional/example_test.py2
-rwxr-xr-xtest/functional/feature_pruning.py2
-rwxr-xr-xtest/functional/feature_segwit.py4
-rwxr-xr-xtest/functional/interface_rest.py32
-rwxr-xr-xtest/functional/p2p_invalid_messages.py85
-rwxr-xr-xtest/functional/p2p_segwit.py2
-rwxr-xr-xtest/functional/p2p_sendheaders.py2
-rwxr-xr-xtest/functional/rpc_deriveaddresses.py50
-rwxr-xr-xtest/functional/rpc_psbt.py5
-rwxr-xr-xtest/functional/rpc_rawtransaction.py3
-rwxr-xr-xtest/functional/test_framework/mininode.py6
-rwxr-xr-xtest/functional/test_framework/test_framework.py17
-rwxr-xr-xtest/functional/test_framework/test_node.py119
-rwxr-xr-xtest/functional/test_runner.py22
-rwxr-xr-xtest/functional/tool_wallet.py101
-rwxr-xr-xtest/functional/wallet_abandonconflict.py46
-rwxr-xr-xtest/functional/wallet_basic.py54
-rwxr-xr-xtest/functional/wallet_createwallet.py100
-rwxr-xr-xtest/functional/wallet_disableprivatekeys.py35
-rwxr-xr-xtest/functional/wallet_importmulti.py85
-rwxr-xr-xtest/functional/wallet_multiwallet.py8
-rwxr-xr-xtest/lint/check-doc.py4
-rwxr-xr-xtest/lint/lint-python-dead-code.sh2
-rwxr-xr-xtest/lint/lint-python.sh148
-rw-r--r--test/sanitizer_suppressions/ubsan1
27 files changed, 791 insertions, 203 deletions
diff --git a/test/README.md b/test/README.md
index b5cbe1aff3..9d4351b1de 100644
--- a/test/README.md
+++ b/test/README.md
@@ -175,7 +175,28 @@ cat /tmp/user/1000/testo9vsdjo3/node1/regtest/bitcoind.pid
gdb /home/example/bitcoind <pid>
```
-Note: gdb attach step may require `sudo`
+Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`.
+See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt
+
+##### Profiling
+
+An easy way to profile node performance during functional tests is provided
+for Linux platforms using `perf`.
+
+Perf will sample the running node and will generate profile data in the node's
+datadir. The profile data can then be presented using `perf report` or a graphical
+tool like [hotspot](https://github.com/KDAB/hotspot).
+
+To generate a profile during test suite runs, use the `--perf` flag.
+
+To see render the output to text, run
+
+```sh
+perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less
+```
+
+For ways to generate more granular profiles, see the README in
+[test/functional](/test/functional).
### Util tests
diff --git a/test/functional/README.md b/test/functional/README.md
index bce0d5db2e..5e3009e6af 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -26,7 +26,7 @@ don't have test cases for.
The Travis linter also checks this, but [possibly not in all cases](https://github.com/bitcoin/bitcoin/pull/14884#discussion_r239585126).
- See [the python lint script](/test/lint/lint-python.sh) that checks for violations that
could lead to bugs and issues in the test code.
-- Avoid wildcard imports where possible
+- Avoid wildcard imports
- Use a module-level docstring to describe what the test is testing, and how it
is testing it.
- When subclassing the BitcoinTestFramwork, place overrides for the
@@ -43,6 +43,7 @@ don't have test cases for.
- `mining` for tests for mining features, eg `mining_prioritisetransaction.py`
- `p2p` for tests that explicitly test the p2p interface, eg `p2p_disconnect_ban.py`
- `rpc` for tests for individual RPC methods or features, eg `rpc_listtransactions.py`
+ - `tool` for tests for tools, eg `tool_wallet.py`
- `wallet` for tests for wallet features, eg `wallet_keypool.py`
- use an underscore to separate words
- exception: for tests for specific RPCs or command line options which don't include underscores, name the test after the exact RPC or argument name, eg `rpc_decodescript.py`, not `rpc_decode_script.py`
@@ -122,3 +123,36 @@ Helpers for script.py
#### [test_framework/blocktools.py](test_framework/blocktools.py)
Helper functions for creating blocks and transactions.
+
+### Benchmarking with perf
+
+An easy way to profile node performance during functional tests is provided
+for Linux platforms using `perf`.
+
+Perf will sample the running node and will generate profile data in the node's
+datadir. The profile data can then be presented using `perf report` or a graphical
+tool like [hotspot](https://github.com/KDAB/hotspot).
+
+There are two ways of invoking perf: one is to use the `--perf` flag when
+running tests, which will profile each node during the entire test run: perf
+begins to profile when the node starts and ends when it shuts down. The other
+way is the use the `profile_with_perf` context manager, e.g.
+
+```python
+with node.profile_with_perf("send-big-msgs"):
+ # Perform activity on the node you're interested in profiling, e.g.:
+ for _ in range(10000):
+ node.p2p.send_message(some_large_message)
+```
+
+To see useful textual output, run
+
+```sh
+perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less
+```
+
+#### See also:
+
+- [Installing perf](https://askubuntu.com/q/50145)
+- [Perf examples](http://www.brendangregg.com/perf.html)
+- [Hotspot](https://github.com/KDAB/hotspot): a GUI for perf output analysis
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index be3544ee74..f367e4fca8 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -13,7 +13,7 @@ is testing and *how* it's being tested
# libraries then local imports).
from collections import defaultdict
-# Avoid wildcard * imports if possible
+# Avoid wildcard * imports
from test_framework.blocktools import (create_block, create_coinbase)
from test_framework.messages import CInv
from test_framework.mininode import (
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index 9a3f4fae45..3e1ba88f0a 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -320,7 +320,7 @@ class PruneTest(BitcoinTestFramework):
if has_block(3):
raise AssertionError("blk00003.dat is still there, should be pruned by now")
- # stop node, start back up with auto-prune at 550MB, make sure still runs
+ # 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"])
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 4bcdf9af55..1efc50e71f 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -43,22 +43,26 @@ class SegWitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 3
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
+ # TODO: remove -txindex. Currently required for getrawtransaction call.
self.extra_args = [
[
"-rpcserialversion=0",
"-vbparams=segwit:0:999999999999",
"-addresstype=legacy",
+ "-txindex"
],
[
"-blockversion=4",
"-rpcserialversion=1",
"-vbparams=segwit:0:999999999999",
"-addresstype=legacy",
+ "-txindex"
],
[
"-blockversion=536870915",
"-vbparams=segwit:0:999999999999",
"-addresstype=legacy",
+ "-txindex"
],
]
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index afa9de580f..f850a54462 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -41,7 +41,8 @@ class RESTTest (BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
- self.extra_args = [["-rest"], []]
+ # TODO: remove -txindex. Currently required for getrawtransaction call.
+ self.extra_args = [["-rest", "-txindex"], []]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -198,9 +199,19 @@ class RESTTest (BitcoinTestFramework):
self.nodes[0].generate(1) # generate block to not affect upcoming tests
self.sync_all()
- self.log.info("Test the /block and /headers URIs")
+ self.log.info("Test the /block, /blockhashbyheight and /headers URIs")
bb_hash = self.nodes[0].getbestblockhash()
+ # Check result if block does not exists
+ assert_equal(self.test_rest_request('/headers/1/0000000000000000000000000000000000000000000000000000000000000000'), [])
+ self.test_rest_request('/block/0000000000000000000000000000000000000000000000000000000000000000', status=404, ret_type=RetType.OBJ)
+
+ # Check result if block is not in the active chain
+ self.nodes[0].invalidateblock(bb_hash)
+ assert_equal(self.test_rest_request('/headers/1/{}'.format(bb_hash)), [])
+ self.test_rest_request('/block/{}'.format(bb_hash))
+ self.nodes[0].reconsiderblock(bb_hash)
+
# Check binary format
response = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ)
assert_greater_than(int(response.getheader('content-length')), 80)
@@ -227,6 +238,23 @@ class RESTTest (BitcoinTestFramework):
# Check json format
block_json_obj = self.test_rest_request("/block/{}".format(bb_hash))
assert_equal(block_json_obj['hash'], bb_hash)
+ assert_equal(self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']))['blockhash'], bb_hash)
+
+ # Check hex/bin format
+ resp_hex = self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']), req_type=ReqType.HEX, ret_type=RetType.OBJ)
+ assert_equal(resp_hex.read().decode('utf-8').rstrip(), bb_hash)
+ resp_bytes = self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']), req_type=ReqType.BIN, ret_type=RetType.BYTES)
+ blockhash = binascii.hexlify(resp_bytes[::-1]).decode('utf-8')
+ assert_equal(blockhash, bb_hash)
+
+ # Check invalid blockhashbyheight requests
+ resp = self.test_rest_request("/blockhashbyheight/abc", ret_type=RetType.OBJ, status=400)
+ assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: abc")
+ resp = self.test_rest_request("/blockhashbyheight/1000000", ret_type=RetType.OBJ, status=404)
+ assert_equal(resp.read().decode('utf-8').rstrip(), "Block height out of range")
+ resp = self.test_rest_request("/blockhashbyheight/-1", ret_type=RetType.OBJ, status=400)
+ assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: -1")
+ self.test_rest_request("/blockhashbyheight/", ret_type=RetType.OBJ, status=400)
# Compare with json block header
json_obj = self.test_rest_request("/headers/1/{}".format(bb_hash))
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index dbc5c5fff6..700fdf6e04 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2015-2018 The Bitcoin Core developers
+# Copyright (c) 2015-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 responses to invalid network messages."""
@@ -16,7 +16,7 @@ class msg_unrecognized:
command = b'badmsg'
- def __init__(self, str_data):
+ def __init__(self, *, str_data):
self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data
def serialize(self):
@@ -26,19 +26,14 @@ class msg_unrecognized:
return "{}(data={})".format(self.command, self.str_data)
-class msg_nametoolong(msg_unrecognized):
-
- command = b'thisnameiswayyyyyyyyytoolong'
-
-
class InvalidMessagesTest(BitcoinTestFramework):
-
def set_test_params(self):
self.num_nodes = 1
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.
@@ -46,10 +41,12 @@ class InvalidMessagesTest(BitcoinTestFramework):
2. Send a few messages with an incorrect data size in the header, ensure the
messages are ignored.
-
- 3. Send an unrecognized message with a command name longer than 12 characters.
-
"""
+ self.test_magic_bytes()
+ self.test_checksum()
+ self.test_size()
+ self.test_command()
+
node = self.nodes[0]
self.node = node
node.add_p2p_connection(P2PDataStore())
@@ -64,7 +61,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
# Send as large a message as is valid, ensure we aren't disconnected but
# also can't exhaust resources.
#
- msg_at_size = msg_unrecognized("b" * valid_data_limit)
+ msg_at_size = msg_unrecognized(str_data="b" * valid_data_limit)
assert len(msg_at_size.serialize()) == msg_limit
increase_allowed = 0.5
@@ -94,10 +91,10 @@ class InvalidMessagesTest(BitcoinTestFramework):
#
# Send an oversized message, ensure we're disconnected.
#
- msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1))
+ msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1))
assert len(msg_over_size.serialize()) == (msg_limit + 1)
- with node.assert_debug_log(["Oversized message from peer=0, disconnecting"]):
+ with node.assert_debug_log(["Oversized message from peer=4, disconnecting"]):
# 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)
@@ -113,7 +110,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
# Send messages with an incorrect data size in the header.
#
actual_size = 100
- msg = msg_unrecognized("b" * actual_size)
+ 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):
@@ -140,18 +137,59 @@ class InvalidMessagesTest(BitcoinTestFramework):
node.disconnect_p2ps()
node.add_p2p_connection(P2PDataStore())
- #
- # 3.
- #
- # Send a message with a too-long command name.
- #
- node.p2p.send_message(msg_nametoolong("foobar"))
- node.p2p.wait_for_disconnect(timeout=4)
-
# Node is still up.
conn = node.add_p2p_connection(P2PDataStore())
conn.sync_with_ping()
+ def test_magic_bytes(self):
+ conn = self.nodes[0].add_p2p_connection(P2PDataStore())
+ 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'
+ with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']):
+ conn.send_message(messages.msg_ping(nonce=0xff))
+ conn.wait_for_disconnect(timeout=1)
+ self.nodes[0].disconnect_p2ps()
+
+ def test_checksum(self):
+ conn = self.nodes[0].add_p2p_connection(P2PDataStore())
+ with self.nodes[0].assert_debug_log(['ProcessMessages(badmsg, 2 bytes): CHECKSUM ERROR expected 78df0a04 was ffffffff']):
+ msg = conn.build_message(msg_unrecognized(str_data="d"))
+ cut_len = (
+ 4 + # magic
+ 12 + # command
+ 4 #len
+ )
+ # modify checksum
+ msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:]
+ self.nodes[0].p2p.send_raw_message(msg)
+ conn.sync_with_ping(timeout=1)
+ self.nodes[0].disconnect_p2ps()
+
+ def test_size(self):
+ 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 # command
+ )
+ # 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)
+ conn.wait_for_disconnect(timeout=1)
+ self.nodes[0].disconnect_p2ps()
+
+ def test_command(self):
+ 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")
+ msg.command = b'\xff' * 12
+ msg = conn.build_message(msg)
+ # Modify command
+ msg = msg[:7] + b'\x00' + msg[7 + 1:]
+ self.nodes[0].p2p.send_raw_message(msg)
+ conn.sync_with_ping(timeout=1)
+ self.nodes[0].disconnect_p2ps()
def _tweak_msg_data_size(self, message, wrong_size):
"""
@@ -174,6 +212,5 @@ class InvalidMessagesTest(BitcoinTestFramework):
return raw_msg_with_wrong_size
-
if __name__ == '__main__':
InvalidMessagesTest().main()
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index d95da227e5..8f8e89cf15 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -755,7 +755,7 @@ class SegWitTest(BitcoinTestFramework):
spend_tx.vin[0].scriptSig = CScript([p2wsh_pubkey, b'a'])
spend_tx.rehash()
with self.nodes[0].assert_debug_log(
- expected_msgs=('Not relaying invalid transaction {}'.format(spend_tx.hash), 'was not accepted: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)')):
+ expected_msgs=(spend_tx.hash, 'was not accepted: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)')):
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
# Now put the witness script in the witness, should succeed after
diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py
index c7ae57de86..7d7d251765 100755
--- a/test/functional/p2p_sendheaders.py
+++ b/test/functional/p2p_sendheaders.py
@@ -490,7 +490,7 @@ class SendHeadersTest(BitcoinTestFramework):
# Now announce a header that forks the last two blocks
tip = blocks[0].sha256
- height -= 1
+ height -= 2
blocks = []
# Create extra blocks for later
diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py
new file mode 100755
index 0000000000..2cc5bc974b
--- /dev/null
+++ b/test/functional/rpc_deriveaddresses.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test the deriveaddresses rpc call."""
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal, assert_raises_rpc_error
+
+class DeriveaddressesTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.supports_cli = 1
+
+ def run_test(self):
+ assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, "a")
+
+ descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"
+ address = "bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5"
+
+ assert_equal(self.nodes[0].deriveaddresses(descriptor), [address])
+
+ descriptor_pubkey = "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"
+ address = "bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5"
+
+ assert_equal(self.nodes[0].deriveaddresses(descriptor_pubkey), [address])
+
+ ranged_descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"
+ assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 0, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
+
+ assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)", 0, 2)
+
+ assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)")
+
+ assert_raises_rpc_error(-8, "Missing range end parameter", self.nodes[0].deriveaddresses, "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", 0)
+
+ assert_raises_rpc_error(-8, "Range end should be equal to or greater than begin", self.nodes[0].deriveaddresses, "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", 2, 0)
+
+ assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", -1, 0)
+
+ combo_descriptor = "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"
+ assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", address, "2NDvEwGfpEqJWfybzpKPHF2XH3jwoQV3D7x"])
+
+ hardened_without_privkey_descriptor = "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)"
+ assert_raises_rpc_error(-5, "Cannot derive script without private keys", self.nodes[0].deriveaddresses, hardened_without_privkey_descriptor)
+
+ bare_multisig_descriptor = "multi(1, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"
+ assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, bare_multisig_descriptor)
+
+if __name__ == '__main__':
+ DeriveaddressesTest().main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 272ebe65cb..a82a5d0208 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -19,6 +19,8 @@ class PSBTTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = False
self.num_nodes = 3
+ # TODO: remove -txindex. Currently required for getrawtransaction call.
+ self.extra_args = [[], ["-txindex"], ["-txindex"]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -267,6 +269,9 @@ class PSBTTest(BitcoinTestFramework):
combined = self.nodes[2].combinepsbt(combiner['combine'])
assert_equal(combined, combiner['result'])
+ # Empty combiner test
+ assert_raises_rpc_error(-8, "Parameter 'txs' cannot be empty", self.nodes[0].combinepsbt, [])
+
# Finalizer test
for finalizer in finalizers:
finalized = self.nodes[2].finalizepsbt(finalizer['finalize'], False)['psbt']
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 5b9dbef68d..a97d753626 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -42,7 +42,8 @@ class RawTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
- self.extra_args = [["-addresstype=legacy"], ["-addresstype=legacy"], ["-addresstype=legacy"]]
+ # TODO: remove -txindex. Currently required for getrawtransaction call.
+ self.extra_args = [["-addresstype=legacy", "-txindex"], ["-addresstype=legacy", "-txindex"], ["-addresstype=legacy", "-txindex"]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index ca5734d67d..ac7cc068bd 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -118,7 +118,7 @@ class P2PConnection(asyncio.Protocol):
# The initial message to send after the connection was made:
self.on_connection_send_msg = None
self.recvbuf = b""
- self.network = net
+ self.magic_bytes = MAGIC_BYTES[net]
logger.debug('Connecting to Bitcoin Node: %s:%d' % (self.dstaddr, self.dstport))
loop = NetworkThread.network_event_loop
@@ -170,7 +170,7 @@ class P2PConnection(asyncio.Protocol):
while True:
if len(self.recvbuf) < 4:
return
- if self.recvbuf[:4] != MAGIC_BYTES[self.network]:
+ if self.recvbuf[:4] != self.magic_bytes:
raise ValueError("got garbage %s" % repr(self.recvbuf))
if len(self.recvbuf) < 4 + 12 + 4 + 4:
return
@@ -232,7 +232,7 @@ class P2PConnection(asyncio.Protocol):
"""Build a serialized P2P message"""
command = message.command
data = message.serialize()
- tmsg = MAGIC_BYTES[self.network]
+ tmsg = self.magic_bytes
tmsg += command
tmsg += b"\x00" * (12 - len(command))
tmsg += struct.pack("<I", len(data))
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 352fa32b5b..8c4c0d7226 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -128,6 +128,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="Attach a python debugger if test fails")
parser.add_argument("--usecli", dest="usecli", default=False, action="store_true",
help="use bitcoin-cli instead of RPC for all commands")
+ parser.add_argument("--perf", dest="perf", default=False, action="store_true",
+ help="profile running nodes with perf for the duration of the test")
self.add_options(parser)
self.options = parser.parse_args()
@@ -139,6 +141,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
config = configparser.ConfigParser()
config.read_file(open(self.options.configfile))
+ self.config = config
self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/bitcoind' + config["environment"]["EXEEXT"])
self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/bitcoin-cli' + config["environment"]["EXEEXT"])
@@ -201,11 +204,20 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
node.cleanup_on_exit = False
self.log.info("Note: bitcoinds were not stopped and may still be running")
- if not self.options.nocleanup and not self.options.noshutdown and success != TestStatus.FAILED:
+ should_clean_up = (
+ not self.options.nocleanup and
+ not self.options.noshutdown and
+ success != TestStatus.FAILED and
+ not self.options.perf
+ )
+ if should_clean_up:
self.log.info("Cleaning up {} on exit".format(self.options.tmpdir))
cleanup_tree_on_exit = True
+ elif self.options.perf:
+ self.log.warning("Not cleaning up dir {} due to perf data".format(self.options.tmpdir))
+ cleanup_tree_on_exit = False
else:
- self.log.warning("Not cleaning up dir %s" % self.options.tmpdir)
+ self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
cleanup_tree_on_exit = False
if success == TestStatus.PASSED:
@@ -309,6 +321,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
extra_conf=extra_confs[i],
extra_args=extra_args[i],
use_cli=self.options.usecli,
+ start_perf=self.options.perf,
))
def start_node(self, i, *args, **kwargs):
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 031a8824b1..999ea68254 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -18,6 +18,8 @@ import tempfile
import time
import urllib.parse
import collections
+import shlex
+import sys
from .authproxy import JSONRPCException
from .util import (
@@ -59,7 +61,13 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection."""
- def __init__(self, i, datadir, *, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
+ def __init__(self, i, datadir, *, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False, start_perf=False):
+ """
+ Kwargs:
+ start_perf (bool): If True, begin profiling the node with `perf` as soon as
+ the node starts.
+ """
+
self.index = i
self.datadir = datadir
self.stdout_dir = os.path.join(self.datadir, "stdout")
@@ -87,6 +95,7 @@ class TestNode():
self.cli = TestNodeCLI(bitcoin_cli, self.datadir)
self.use_cli = use_cli
+ self.start_perf = start_perf
self.running = False
self.process = None
@@ -95,6 +104,8 @@ class TestNode():
self.url = None
self.log = logging.getLogger('TestFramework.node%d' % i)
self.cleanup_on_exit = True # Whether to kill the node when this object goes away
+ # Cache perf subprocesses here by their data output filename.
+ self.perf_subprocesses = {}
self.p2ps = []
@@ -186,6 +197,9 @@ class TestNode():
self.running = True
self.log.debug("bitcoind started, waiting for RPC to come up")
+ if self.start_perf:
+ self._start_perf()
+
def wait_for_rpc_connection(self):
"""Sets up an RPC connection to the bitcoind process. Returns False if unable to connect."""
# Poll at a rate of four times per second
@@ -195,12 +209,15 @@ class TestNode():
raise FailedToStartError(self._node_msg(
'bitcoind exited with status {} during initialization'.format(self.process.returncode)))
try:
- self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
- self.rpc.getblockcount()
+ rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
+ rpc.getblockcount()
# If the call to getblockcount() succeeds then the RPC connection is up
+ self.log.debug("RPC successfully started")
+ if self.use_cli:
+ return
+ self.rpc = rpc
self.rpc_connected = True
self.url = self.rpc.url
- self.log.debug("RPC successfully started")
return
except IOError as e:
if e.errno != errno.ECONNREFUSED: # Port not yet open?
@@ -238,6 +255,10 @@ class TestNode():
except http.client.CannotSendRequest:
self.log.exception("Unable to stop node.")
+ # If there are any running perf processes, stop them.
+ for profile_name in tuple(self.perf_subprocesses.keys()):
+ self._stop_perf(profile_name)
+
# Check that stderr is as expected
self.stderr.seek(0)
stderr = self.stderr.read().decode('utf-8').strip()
@@ -317,6 +338,84 @@ class TestNode():
increase_allowed * 100, before_memory_usage, after_memory_usage,
perc_increase_memory_usage * 100))
+ @contextlib.contextmanager
+ def profile_with_perf(self, profile_name):
+ """
+ Context manager that allows easy profiling of node activity using `perf`.
+
+ See `test/functional/README.md` for details on perf usage.
+
+ Args:
+ profile_name (str): This string will be appended to the
+ profile data filename generated by perf.
+ """
+ subp = self._start_perf(profile_name)
+
+ yield
+
+ if subp:
+ self._stop_perf(profile_name)
+
+ def _start_perf(self, profile_name=None):
+ """Start a perf process to profile this node.
+
+ Returns the subprocess running perf."""
+ subp = None
+
+ def test_success(cmd):
+ return subprocess.call(
+ # shell=True required for pipe use below
+ cmd, shell=True,
+ stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0
+
+ if not sys.platform.startswith('linux'):
+ self.log.warning("Can't profile with perf; only availabe on Linux platforms")
+ return None
+
+ if not test_success('which perf'):
+ self.log.warning("Can't profile with perf; must install perf-tools")
+ return None
+
+ if not test_success('readelf -S {} | grep .debug_str'.format(shlex.quote(self.binary))):
+ self.log.warning(
+ "perf output won't be very useful without debug symbols compiled into bitcoind")
+
+ output_path = tempfile.NamedTemporaryFile(
+ dir=self.datadir,
+ prefix="{}.perf.data.".format(profile_name or 'test'),
+ delete=False,
+ ).name
+
+ cmd = [
+ 'perf', 'record',
+ '-g', # Record the callgraph.
+ '--call-graph', 'dwarf', # Compatibility for gcc's --fomit-frame-pointer.
+ '-F', '101', # Sampling frequency in Hz.
+ '-p', str(self.process.pid),
+ '-o', output_path,
+ ]
+ subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.perf_subprocesses[profile_name] = subp
+
+ return subp
+
+ def _stop_perf(self, profile_name):
+ """Stop (and pop) a perf subprocess."""
+ subp = self.perf_subprocesses.pop(profile_name)
+ output_path = subp.args[subp.args.index('-o') + 1]
+
+ subp.terminate()
+ subp.wait(timeout=10)
+
+ stderr = subp.stderr.read().decode()
+ if 'Consider tweaking /proc/sys/kernel/perf_event_paranoid' in stderr:
+ self.log.warning(
+ "perf couldn't collect data! Try "
+ "'sudo sysctl -w kernel.perf_event_paranoid=-1'")
+ else:
+ report_cmd = "perf report -i {}".format(output_path)
+ self.log.info("See perf output by running '{}'".format(report_cmd))
+
def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs):
"""Attempt to start the node and expect it to raise an error.
@@ -402,6 +501,14 @@ class TestNodeCLIAttr:
def get_request(self, *args, **kwargs):
return lambda: self(*args, **kwargs)
+def arg_to_cli(arg):
+ if isinstance(arg, bool):
+ return str(arg).lower()
+ elif isinstance(arg, dict) or isinstance(arg, list):
+ return json.dumps(arg)
+ else:
+ return str(arg)
+
class TestNodeCLI():
"""Interface to bitcoin-cli for an individual node"""
@@ -433,8 +540,8 @@ class TestNodeCLI():
def send_cli(self, command=None, *args, **kwargs):
"""Run bitcoin-cli command. Deserializes returned string as python object."""
- pos_args = [str(arg).lower() if type(arg) is bool else str(arg) for arg in args]
- named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()]
+ pos_args = [arg_to_cli(arg) for arg in args]
+ named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()]
assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
p_args = [self.binary, "-datadir=" + self.datadir] + self.options
if named_args:
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 8c6f6706e7..999cd72108 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -7,8 +7,6 @@
This module calls down into individual test cases via subprocess. It will
forward all unrecognized arguments onto the individual test scripts.
-Functional tests are disabled on Windows by default. Use --force to run them anyway.
-
For a description of arguments recognized by test scripts, see
`test/functional/test_framework/test_framework.py:BitcoinTestFramework.main`.
@@ -108,6 +106,7 @@ BASE_SCRIPTS = [
'interface_bitcoin_cli.py',
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
+ 'tool_wallet.py',
'wallet_txn_clone.py',
'wallet_txn_clone.py --segwit',
'rpc_getchaintips.py',
@@ -117,8 +116,8 @@ BASE_SCRIPTS = [
'mempool_persist.py',
'wallet_multiwallet.py',
'wallet_multiwallet.py --usecli',
- 'wallet_disableprivatekeys.py',
- 'wallet_disableprivatekeys.py --usecli',
+ 'wallet_createwallet.py',
+ 'wallet_createwallet.py --usecli',
'interface_http.py',
'interface_rpc.py',
'rpc_psbt.py',
@@ -181,6 +180,8 @@ BASE_SCRIPTS = [
'feature_filelock.py',
'p2p_unrequested_blocks.py',
'feature_includeconf.py',
+ 'rpc_deriveaddresses.py',
+ 'rpc_deriveaddresses.py --usecli',
'rpc_scantxoutset.py',
'feature_logging.py',
'p2p_node_network_limited.py',
@@ -223,7 +224,6 @@ def main():
parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment')
parser.add_argument('--exclude', '-x', help='specify a comma-separated-list of scripts to exclude.')
parser.add_argument('--extended', action='store_true', help='run the extended test suite in addition to the basic tests')
- parser.add_argument('--force', '-f', action='store_true', help='run tests even on platforms where they are disabled by default (e.g. windows).')
parser.add_argument('--help', '-h', '-?', action='store_true', help='print help text and exit')
parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.')
parser.add_argument('--keepcache', '-k', action='store_true', help='the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun.')
@@ -250,22 +250,12 @@ def main():
# Create base test directory
tmpdir = "%s/test_runner_₿_🏃_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
- # If we fixed the command-line and filename encoding issue on Windows, these two lines could be removed
- if config["environment"]["EXEEXT"] == ".exe":
- tmpdir = "%s/test_runner_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
-
os.makedirs(tmpdir)
logging.debug("Temporary test directory at %s" % tmpdir)
enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND")
- if config["environment"]["EXEEXT"] == ".exe" and not args.force:
- # https://github.com/bitcoin/bitcoin/commit/d52802551752140cf41f0d9a225a43e84404d3e9
- # https://github.com/bitcoin/bitcoin/pull/5677#issuecomment-136646964
- print("Tests currently disabled on Windows by default. Use --force option to enable")
- sys.exit(0)
-
if not enable_bitcoind:
print("No functional tests to run.")
print("Rerun ./configure with --with-daemon and then make")
@@ -562,7 +552,7 @@ class TestResult():
def check_script_prefixes():
"""Check that test scripts start with one of the allowed name prefixes."""
- good_prefixes_re = re.compile("(example|feature|interface|mempool|mining|p2p|rpc|wallet)_")
+ good_prefixes_re = re.compile("(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool)_")
bad_script_names = [script for script in ALL_SCRIPTS if good_prefixes_re.match(script) is None]
if bad_script_names:
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
new file mode 100755
index 0000000000..fbcf21e729
--- /dev/null
+++ b/test/functional/tool_wallet.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 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 bitcoin-wallet."""
+import subprocess
+import textwrap
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+class ToolWalletTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def bitcoin_wallet_process(self, *args):
+ binary = self.config["environment"]["BUILDDIR"] + '/src/bitcoin-wallet' + self.config["environment"]["EXEEXT"]
+ args = ['-datadir={}'.format(self.nodes[0].datadir), '-regtest'] + list(args)
+ return subprocess.Popen([binary] + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+
+ def assert_raises_tool_error(self, error, *args):
+ p = self.bitcoin_wallet_process(*args)
+ stdout, stderr = p.communicate()
+ assert_equal(p.poll(), 1)
+ assert_equal(stdout, '')
+ assert_equal(stderr.strip(), error)
+
+ def assert_tool_output(self, output, *args):
+ p = self.bitcoin_wallet_process(*args)
+ stdout, stderr = p.communicate()
+ assert_equal(p.poll(), 0)
+ assert_equal(stderr, '')
+ assert_equal(stdout, output)
+
+ def run_test(self):
+
+ self.assert_raises_tool_error('Invalid command: foo', 'foo')
+ # `bitcoin-wallet help` is an error. Use `bitcoin-wallet -help`
+ self.assert_raises_tool_error('Invalid command: help', 'help')
+ self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
+ self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
+ self.assert_raises_tool_error('Error loading wallet.dat. Is wallet being used by other process?', '-wallet=wallet.dat', 'info')
+ self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info')
+
+ # stop the node to close the wallet to call info command
+ self.stop_node(0)
+
+ out = textwrap.dedent('''\
+ Wallet info
+ ===========
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2
+ Transactions: 0
+ Address Book: 3
+ ''')
+ self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
+
+ # mutate the wallet to check the info command output changes accordingly
+ self.start_node(0)
+ self.nodes[0].generate(1)
+ self.stop_node(0)
+
+ out = textwrap.dedent('''\
+ Wallet info
+ ===========
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2
+ Transactions: 1
+ Address Book: 3
+ ''')
+ self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
+
+ out = textwrap.dedent('''\
+ Topping up keypool...
+ Wallet info
+ ===========
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2000
+ Transactions: 0
+ Address Book: 0
+ ''')
+ self.assert_tool_output(out, '-wallet=foo', 'create')
+
+ self.start_node(0, ['-wallet=foo'])
+ out = self.nodes[0].getwalletinfo()
+ self.stop_node(0)
+
+ assert_equal(0, out['txcount'])
+ assert_equal(1000, out['keypoolsize'])
+ assert_equal(1000, out['keypoolsize_hd_internal'])
+ assert_equal(True, 'hdseedid' in out)
+
+if __name__ == '__main__':
+ ToolWalletTest().main()
diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py
index e5ac2c8bd4..0c3c247694 100755
--- a/test/functional/wallet_abandonconflict.py
+++ b/test/functional/wallet_abandonconflict.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2014-2018 The Bitcoin Core developers
+# Copyright (c) 2014-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 the abandontransaction RPC.
@@ -13,12 +13,21 @@
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes, disconnect_nodes, sync_blocks, sync_mempools
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+ connect_nodes,
+ disconnect_nodes,
+ sync_blocks,
+ sync_mempools,
+)
+
class AbandonConflictTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- self.extra_args = [["-minrelaytxfee=0.00001"], []]
+ # TODO: remove -txindex. Currently required for getrawtransaction call.
+ self.extra_args = [["-minrelaytxfee=0.00001", "-txindex"], []]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -40,21 +49,21 @@ class AbandonConflictTest(BitcoinTestFramework):
sync_blocks(self.nodes)
newbalance = self.nodes[0].getbalance()
- assert(balance - newbalance < Decimal("0.001")) #no more than fees lost
+ assert balance - newbalance < Decimal("0.001") #no more than fees lost
balance = newbalance
# Disconnect nodes so node0's transactions don't get into node1's mempool
disconnect_nodes(self.nodes[0], 1)
# Identify the 10btc outputs
- nA = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txA, 1)["vout"]) if vout["value"] == Decimal("10"))
- nB = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txB, 1)["vout"]) if vout["value"] == Decimal("10"))
- nC = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txC, 1)["vout"]) if vout["value"] == Decimal("10"))
+ nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10"))
+ nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10"))
+ nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10"))
- inputs =[]
+ inputs = []
# spend 10btc outputs from txA and txB
- inputs.append({"txid":txA, "vout":nA})
- inputs.append({"txid":txB, "vout":nB})
+ inputs.append({"txid": txA, "vout": nA})
+ inputs.append({"txid": txB, "vout": nB})
outputs = {}
outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998")
@@ -63,12 +72,12 @@ class AbandonConflictTest(BitcoinTestFramework):
txAB1 = self.nodes[0].sendrawtransaction(signed["hex"])
# Identify the 14.99998btc output
- nAB = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txAB1, 1)["vout"]) if vout["value"] == Decimal("14.99998"))
+ nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998"))
#Create a child tx spending AB1 and C
inputs = []
- inputs.append({"txid":txAB1, "vout":nAB})
- inputs.append({"txid":txC, "vout":nC})
+ inputs.append({"txid": txAB1, "vout": nAB})
+ inputs.append({"txid": txC, "vout": nC})
outputs = {}
outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996")
signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs))
@@ -76,8 +85,8 @@ class AbandonConflictTest(BitcoinTestFramework):
# Create a child tx spending ABC2
signed3_change = Decimal("24.999")
- inputs = [ {"txid":txABC2, "vout":0} ]
- outputs = { self.nodes[0].getnewaddress(): signed3_change }
+ inputs = [{"txid": txABC2, "vout": 0}]
+ outputs = {self.nodes[0].getnewaddress(): signed3_change}
signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs))
# note tx is never directly referenced, only abandoned as a child of the above
self.nodes[0].sendrawtransaction(signed3["hex"])
@@ -105,7 +114,7 @@ class AbandonConflictTest(BitcoinTestFramework):
unconfbalance = self.nodes[0].getunconfirmedbalance() + self.nodes[0].getbalance()
assert_equal(unconfbalance, newbalance)
# Also shouldn't show up in listunspent
- assert(not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)])
+ assert not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)]
balance = newbalance
# Abandon original transaction and verify inputs are available again
@@ -145,8 +154,8 @@ class AbandonConflictTest(BitcoinTestFramework):
# Create a double spend of AB1 by spending again from only A's 10 output
# Mine double spend from node 1
- inputs =[]
- inputs.append({"txid":txA, "vout":nA})
+ inputs = []
+ inputs.append({"txid": txA, "vout": nA})
outputs = {}
outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999")
tx = self.nodes[0].createrawtransaction(inputs, outputs)
@@ -172,5 +181,6 @@ class AbandonConflictTest(BitcoinTestFramework):
self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315")
self.log.info(str(balance) + " -> " + str(newbalance) + " ?")
+
if __name__ == '__main__':
AbandonConflictTest().main()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 7184bb8cb6..fe1a614700 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2014-2018 The Bitcoin Core developers
+# Copyright (c) 2014-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 the wallet."""
@@ -19,10 +19,13 @@ from test_framework.util import (
wait_until,
)
+
class WalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
+ # TODO: remove -txindex. Currently required for getrawtransaction call.
+ self.extra_args = [[], [], ["-txindex"], []]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -194,7 +197,7 @@ class WalletTest(BitcoinTestFramework):
txid = self.nodes[2].sendtoaddress(address, 10, "", "", False)
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
- node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
+ node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
assert_equal(self.nodes[0].getbalance(), Decimal('10'))
# Send 10 BTC with subtract fee from amount
@@ -203,14 +206,14 @@ class WalletTest(BitcoinTestFramework):
self.sync_all([self.nodes[0:3]])
node_2_bal -= Decimal('10')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
- node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
+ node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
# Sendmany 10 BTC
txid = self.nodes[2].sendmany('', {address: 10}, 0, "", [])
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
node_0_bal += Decimal('10')
- node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid)))
+ node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
assert_equal(self.nodes[0].getbalance(), node_0_bal)
# Sendmany 10 BTC with subtract fee from amount
@@ -219,7 +222,7 @@ class WalletTest(BitcoinTestFramework):
self.sync_all([self.nodes[0:3]])
node_2_bal -= Decimal('10')
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].getrawtransaction(txid)))
+ 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']))
# Test ResendWalletTransactions:
# Create a couple of transactions, then start up a fourth
@@ -237,7 +240,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(set(relayed), {txid1, txid2})
sync_mempools(self.nodes)
- assert(txid1 in self.nodes[3].getrawmempool())
+ assert txid1 in self.nodes[3].getrawmempool()
# check if we can list zero value tx as available coins
# 1. create raw_tx
@@ -264,7 +267,7 @@ class WalletTest(BitcoinTestFramework):
if uTx['txid'] == zero_value_txid:
found = True
assert_equal(uTx['amount'], Decimal('0'))
- assert(found)
+ assert found
# do some -walletbroadcast tests
self.stop_nodes()
@@ -324,12 +327,38 @@ class WalletTest(BitcoinTestFramework):
tx_obj = self.nodes[0].gettransaction(txid)
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
+ # General checks for errors from incorrect inputs
# 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")
# This will raise an exception since generate does not accept a string
assert_raises_rpc_error(-1, "not an integer", self.nodes[0].generate, "2")
+ # This will raise an exception for the invalid private key format
+ assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")
+
+ # This will raise an exception for importing an address with the PS2H flag
+ temp_address = self.nodes[1].getnewaddress()
+ assert_raises_rpc_error(-5, "Cannot use the p2sh flag with an address - use a script instead", self.nodes[0].importaddress, temp_address, "label", False, True)
+
+ # This will raise an exception for attempting to dump the private key of an address you do not own
+ assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address)
+
+ # This will raise an exception for attempting to get the private key of an invalid Bitcoin address
+ assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid")
+
+ # This will raise an exception for attempting to set a label for an invalid Bitcoin address
+ assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label")
+
+ # This will raise an exception for importing an invalid address
+ assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
+
+ # This will raise an exception for attempting to import a pubkey that isn't in hex
+ assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex")
+
+ # This will raise an exception for importing an invalid pubkey
+ assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
+
# Import address and private key to check correct behavior of spendable unspents
# 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
@@ -341,7 +370,7 @@ class WalletTest(BitcoinTestFramework):
self.nodes[1].importaddress(address_to_import)
# 3. Validate that the imported address is watch-only on node1
- assert(self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"])
+ assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]
# 4. Check that the unspents after import are not spendable
assert_array_result(self.nodes[1].listunspent(),
@@ -383,7 +412,7 @@ class WalletTest(BitcoinTestFramework):
addr = self.nodes[0].getnewaddress()
self.nodes[0].setlabel(addr, label)
assert_equal(self.nodes[0].getaddressinfo(addr)['label'], label)
- assert(label in self.nodes[0].listlabels())
+ assert label in self.nodes[0].listlabels()
self.nodes[0].rpc.ensure_ascii = True # restore to default
# maintenance tests
@@ -442,8 +471,8 @@ class WalletTest(BitcoinTestFramework):
# Without walletrejectlongchains, we will still generate a txid
# The tx will be stored in the wallet but not accepted to the mempool
extra_txid = self.nodes[0].sendtoaddress(sending_addr, Decimal('0.0001'))
- assert(extra_txid not in self.nodes[0].getrawmempool())
- assert(extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()])
+ assert extra_txid not in self.nodes[0].getrawmempool()
+ assert extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()]
self.nodes[0].abandontransaction(extra_txid)
total_txs = len(self.nodes[0].listtransactions("*", 99999))
@@ -480,7 +509,7 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
destination = self.nodes[1].getnewaddress()
txid = self.nodes[0].sendtoaddress(destination, 0.123)
- tx = self.nodes[0].decoderawtransaction(self.nodes[0].getrawtransaction(txid))
+ tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex'])
output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]]
assert len(output_addresses) > 1
for address in output_addresses:
@@ -491,5 +520,6 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].setlabel(change, 'foobar')
assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False)
+
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
new file mode 100755
index 0000000000..9fd2650d78
--- /dev/null
+++ b/test/functional/wallet_createwallet.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 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 createwallet arguments.
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
+
+class CreateWalletTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+ self.supports_cli = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ node = self.nodes[0]
+ node.generate(1) # Leave IBD for sethdseed
+
+ self.nodes[0].createwallet(wallet_name='w0')
+ w0 = node.get_wallet_rpc('w0')
+ address1 = w0.getnewaddress()
+
+ self.log.info("Test disableprivatekeys creation.")
+ self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
+ w1 = node.get_wallet_rpc('w1')
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w1.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
+ w1.importpubkey(w0.getaddressinfo(address1)['pubkey'])
+
+ self.log.info('Test that private keys cannot be imported')
+ addr = w0.getnewaddress('', 'legacy')
+ privkey = w0.dumpprivkey(addr)
+ assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey)
+ result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}])
+ assert(not result[0]['success'])
+ assert('warning' not in result[0])
+ assert_equal(result[0]['error']['code'], -4)
+ assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
+
+ self.log.info("Test blank creation with private keys disabled.")
+ self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
+ w2 = node.get_wallet_rpc('w2')
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getrawchangeaddress)
+ w2.importpubkey(w0.getaddressinfo(address1)['pubkey'])
+
+ self.log.info("Test blank creation with private keys enabled.")
+ self.nodes[0].createwallet(wallet_name='w3', disable_private_keys=False, blank=True)
+ w3 = node.get_wallet_rpc('w3')
+ assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress)
+ # Import private key
+ w3.importprivkey(w0.dumpprivkey(address1))
+ # Imported private keys are currently ignored by the keypool
+ assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
+ # Set the seed
+ w3.sethdseed()
+ assert_equal(w3.getwalletinfo()['keypoolsize'], 1)
+ w3.getnewaddress()
+ w3.getrawchangeaddress()
+
+ self.log.info("Test blank creation with privkeys enabled and then encryption")
+ self.nodes[0].createwallet(wallet_name='w4', disable_private_keys=False, blank=True)
+ w4 = node.get_wallet_rpc('w4')
+ assert_equal(w4.getwalletinfo()['keypoolsize'], 0)
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
+ # Encrypt the wallet. Nothing should change about the keypool
+ w4.encryptwallet('pass')
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
+ # Now set a seed and it should work. Wallet should also be encrypted
+ w4.walletpassphrase('pass', 2)
+ w4.sethdseed()
+ w4.getnewaddress()
+ w4.getrawchangeaddress()
+
+ self.log.info("Test blank creation with privkeys disabled and then encryption")
+ self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
+ w5 = node.get_wallet_rpc('w5')
+ assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
+ # Encrypt the wallet
+ w5.encryptwallet('pass')
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
+ assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
+
+if __name__ == '__main__':
+ CreateWalletTest().main()
diff --git a/test/functional/wallet_disableprivatekeys.py b/test/functional/wallet_disableprivatekeys.py
deleted file mode 100755
index 34ff525255..0000000000
--- a/test/functional/wallet_disableprivatekeys.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2018 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 disable-privatekeys mode.
-"""
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import (
- assert_raises_rpc_error,
-)
-
-
-class DisablePrivateKeysTest(BitcoinTestFramework):
- def set_test_params(self):
- self.setup_clean_chain = False
- self.num_nodes = 1
- self.supports_cli = True
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
- def run_test(self):
- node = self.nodes[0]
- self.log.info("Test disableprivatekeys creation.")
- self.nodes[0].createwallet('w1', True)
- self.nodes[0].createwallet('w2')
- w1 = node.get_wallet_rpc('w1')
- w2 = node.get_wallet_rpc('w2')
- assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getnewaddress)
- assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
- w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey'])
-
-if __name__ == '__main__':
- DisablePrivateKeysTest().main()
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index f122f19e3a..7cce72b39f 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -203,7 +203,7 @@ class ImportMultiTest(BitcoinTestFramework):
"keys": [key.privkey]},
success=False,
error_code=-4,
- error_message='The wallet already contains the private key for this address or script')
+ error_message='The wallet already contains the private key for this address or script ("' + key.p2pkh_script + '")')
# Address + Private key + watchonly
self.log.info("Should import an address with private key and with watchonly")
@@ -543,5 +543,88 @@ class ImportMultiTest(BitcoinTestFramework):
solvable=True,
ismine=False)
+ # Test importing of a P2SH-P2WPKH address via descriptor + private key
+ key = get_key(self.nodes[0])
+ self.log.info("Should import a p2sh-p2wpkh address from descriptor and private key")
+ self.test_importmulti({"desc": "sh(wpkh(" + key.pubkey + "))",
+ "timestamp": "now",
+ "label": "Descriptor import test",
+ "keys": [key.privkey]},
+ success=True)
+ test_address(self.nodes[1],
+ key.p2sh_p2wpkh_addr,
+ solvable=True,
+ ismine=True,
+ label="Descriptor import test")
+
+ # Test ranged descriptor fails if range is not specified
+ xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
+ addresses = ["2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1'
+ desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))"
+ self.log.info("Ranged descriptor import should fail without a specified range")
+ self.test_importmulti({"desc": desc,
+ "timestamp": "now"},
+ success=False,
+ error_code=-8,
+ error_message='Descriptor is ranged, please specify the range')
+
+ # Test importing of a ranged descriptor without keys
+ self.log.info("Should import the ranged descriptor with specified range as solvable")
+ self.test_importmulti({"desc": desc,
+ "timestamp": "now",
+ "range": {"end": 1}},
+ success=True,
+ warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
+ for address in addresses:
+ test_address(self.nodes[1],
+ key.p2sh_p2wpkh_addr,
+ solvable=True)
+
+ # Test importing of a P2PKH address via descriptor
+ key = get_key(self.nodes[0])
+ self.log.info("Should import a p2pkh address from descriptor")
+ self.test_importmulti({"desc": "pkh(" + key.pubkey + ")",
+ "timestamp": "now",
+ "label": "Descriptor import test"},
+ True,
+ warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
+ test_address(self.nodes[1],
+ key.p2pkh_addr,
+ solvable=True,
+ ismine=False,
+ label="Descriptor import test")
+
+ # Test import fails if both desc and scriptPubKey are provided
+ key = get_key(self.nodes[0])
+ self.log.info("Import should fail if both scriptPubKey and desc are provided")
+ self.test_importmulti({"desc": "pkh(" + key.pubkey + ")",
+ "scriptPubKey": {"address": key.p2pkh_addr},
+ "timestamp": "now"},
+ success=False,
+ error_code=-8,
+ error_message='Both a descriptor and a scriptPubKey should not be provided.')
+
+ # Test import fails if neither desc nor scriptPubKey are present
+ key = get_key(self.nodes[0])
+ self.log.info("Import should fail if neither a descriptor nor a scriptPubKey are provided")
+ self.test_importmulti({"timestamp": "now"},
+ success=False,
+ error_code=-8,
+ error_message='Either a descriptor or scriptPubKey must be provided.')
+
+ # Test importing of a multisig via descriptor
+ key1 = get_key(self.nodes[0])
+ key2 = get_key(self.nodes[0])
+ self.log.info("Should import a 1-of-2 bare multisig from descriptor")
+ self.test_importmulti({"desc": "multi(1," + key1.pubkey + "," + key2.pubkey + ")",
+ "timestamp": "now"},
+ success=True)
+ self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
+ test_address(self.nodes[1],
+ key1.p2pkh_addr,
+ ismine=False,
+ iswatchonly=False)
+
+
if __name__ == '__main__':
ImportMultiTest().main()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 8ab569a3c3..df778f57df 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -315,6 +315,14 @@ class MultiWalletTest(BitcoinTestFramework):
self.nodes[0].loadwallet(wallet_name)
assert_equal(rpc.getaddressinfo(addr)['ismine'], True)
+ # Test .walletlock file is closed
+ self.start_node(1)
+ wallet = os.path.join(self.options.tmpdir, 'my_wallet')
+ self.nodes[0].createwallet(wallet)
+ assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet)
+ self.nodes[0].unloadwallet(wallet)
+ self.nodes[1].loadwallet(wallet)
+
if __name__ == '__main__':
MultiWalletTest().main()
diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py
index 4facd6c334..c370ce0c04 100755
--- a/test/lint/check-doc.py
+++ b/test/lint/check-doc.py
@@ -30,8 +30,8 @@ def main():
used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8')
docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8')
else:
- used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True) # encoding='utf8'
- docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True) # encoding='utf8'
+ used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip()
+ docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip()
args_used = set(re.findall(re.compile(REGEX_ARG), used))
args_docd = set(re.findall(re.compile(REGEX_DOC), docd)).union(SET_DOC_OPTIONAL)
diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh
index 1b897cd131..863caa9d5c 100755
--- a/test/lint/lint-python-dead-code.sh
+++ b/test/lint/lint-python-dead-code.sh
@@ -15,5 +15,5 @@ fi
vulture \
--min-confidence 60 \
- --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey" \
+ --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey,profile_with_perf" \
$(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py")
diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh
index 3dbb9fff28..f5b851aeab 100755
--- a/test/lint/lint-python.sh
+++ b/test/lint/lint-python.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/env bash
#
# Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
@@ -8,77 +8,79 @@
export LC_ALL=C
-# E101 indentation contains mixed spaces and tabs
-# E112 expected an indented block
-# E113 unexpected indentation
-# E115 expected an indented block (comment)
-# E116 unexpected indentation (comment)
-# E125 continuation line with same indent as next logical line
-# E129 visually indented line with same indent as next logical line
-# E131 continuation line unaligned for hanging indent
-# E133 closing bracket is missing indentation
-# E223 tab before operator
-# E224 tab after operator
-# E242 tab after ','
-# E266 too many leading '#' for block comment
-# E271 multiple spaces after keyword
-# E272 multiple spaces before keyword
-# E273 tab after keyword
-# E274 tab before keyword
-# E275 missing whitespace after keyword
-# E304 blank lines found after function decorator
-# E306 expected 1 blank line before a nested definition
-# E401 multiple imports on one line
-# E402 module level import not at top of file
-# F403 'from foo_module import *' used; unable to detect undefined names
-# F405 foo_function may be undefined, or defined from star imports: bar_module
-# E502 the backslash is redundant between brackets
-# E701 multiple statements on one line (colon)
-# E702 multiple statements on one line (semicolon)
-# E703 statement ends with a semicolon
-# 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
-# E902 TokenError: EOF in multi-line string
-# F401 module imported but unused
-# F402 import module from line N shadowed by loop variable
-# F404 future import(s) name after other statements
-# F406 "from module import *" only allowed at module level
-# F407 an undefined __future__ feature name was imported
-# F601 dictionary key name repeated with different values
-# F602 dictionary key variable name repeated with different values
-# F621 too many expressions in an assignment with star-unpacking
-# F622 two or more starred expressions in an assignment (a, *b, *c = d)
-# F631 assertion test is a tuple, which are always True
-# F701 a break statement outside of a while or for loop
-# F702 a continue statement outside of a while or for loop
-# F703 a continue statement in a finally block in a loop
-# F704 a yield or yield from statement outside of a function
-# F705 a return statement with arguments inside a generator
-# F706 a return statement outside of a function/method
-# F707 an except: block as not the last exception handler
-# F811 redefinition of unused name from line N
-# F812 list comprehension redefines 'foo' from line N
-# F821 undefined name 'Foo'
-# F822 undefined name name in __all__
-# F823 local variable name … referenced before assignment
-# F831 duplicate argument name in function definition
-# F841 local variable 'foo' is assigned to but never used
-# W191 indentation contains tabs
-# W291 trailing whitespace
-# W292 no newline at end of file
-# W293 blank line contains whitespace
-# W504 line break after binary operator
-# W601 .has_key() is deprecated, use "in"
-# W602 deprecated form of raising exception
-# W603 "<>" is deprecated, use "!="
-# W604 backticks are deprecated, use "repr()"
-# W605 invalid escape sequence "x"
-# W606 'async' and 'await' are reserved keywords starting with Python 3.7
+enabled=(
+ E101 # indentation contains mixed spaces and tabs
+ E112 # expected an indented block
+ E113 # unexpected indentation
+ E115 # expected an indented block (comment)
+ E116 # unexpected indentation (comment)
+ E125 # continuation line with same indent as next logical line
+ E129 # visually indented line with same indent as next logical line
+ E131 # continuation line unaligned for hanging indent
+ E133 # closing bracket is missing indentation
+ E223 # tab before operator
+ E224 # tab after operator
+ E242 # tab after ','
+ E266 # too many leading '#' for block comment
+ E271 # multiple spaces after keyword
+ E272 # multiple spaces before keyword
+ E273 # tab after keyword
+ E274 # tab before keyword
+ E275 # missing whitespace after keyword
+ E304 # blank lines found after function decorator
+ E306 # expected 1 blank line before a nested definition
+ E401 # multiple imports on one line
+ E402 # module level import not at top of file
+ E502 # the backslash is redundant between brackets
+ E701 # multiple statements on one line (colon)
+ E702 # multiple statements on one line (semicolon)
+ E703 # statement ends with a semicolon
+ 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
+ E902 # TokenError: EOF in multi-line string
+ F401 # module imported but unused
+ F402 # import module from line N shadowed by loop variable
+ F403 # 'from foo_module import *' used; unable to detect undefined names
+ F404 # future import(s) name after other statements
+ F405 # foo_function may be undefined, or defined from star imports: bar_module
+ F406 # "from module import *" only allowed at module level
+ F407 # an undefined __future__ feature name was imported
+ F601 # dictionary key name repeated with different values
+ F602 # dictionary key variable name repeated with different values
+ F621 # too many expressions in an assignment with star-unpacking
+ F622 # two or more starred expressions in an assignment (a, *b, *c = d)
+ F631 # assertion test is a tuple, which are always True
+ F701 # a break statement outside of a while or for loop
+ F702 # a continue statement outside of a while or for loop
+ F703 # a continue statement in a finally block in a loop
+ F704 # a yield or yield from statement outside of a function
+ F705 # a return statement with arguments inside a generator
+ F706 # a return statement outside of a function/method
+ F707 # an except: block as not the last exception handler
+ F811 # redefinition of unused name from line N
+ F812 # list comprehension redefines 'foo' from line N
+ F821 # undefined name 'Foo'
+ F822 # undefined name name in __all__
+ F823 # local variable name … referenced before assignment
+ F831 # duplicate argument name in function definition
+ F841 # local variable 'foo' is assigned to but never used
+ W191 # indentation contains tabs
+ W291 # trailing whitespace
+ W292 # no newline at end of file
+ W293 # blank line contains whitespace
+ W504 # line break after binary operator
+ W601 # .has_key() is deprecated, use "in"
+ W602 # deprecated form of raising exception
+ W603 # "<>" is deprecated, use "!="
+ W604 # backticks are deprecated, use "repr()"
+ W605 # invalid escape sequence "x"
+ W606 # 'async' and 'await' are reserved keywords starting with Python 3.7
+)
if ! command -v flake8 > /dev/null; then
echo "Skipping Python linting since flake8 is not installed. Install by running \"pip3 install flake8\""
@@ -88,4 +90,4 @@ elif PYTHONWARNINGS="ignore" flake8 --version | grep -q "Python 2"; then
exit 0
fi
-PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=E101,E112,E113,E115,E116,E125,E129,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E711,E714,E721,E741,E742,E743,E901,E902,F401,F402,F403,F404,F405,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W191,W291,W292,W293,W504,W601,W602,W603,W604,W605,W606 "${@:-.}"
+PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; echo "${enabled[*]}") "${@:-.}"
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index f0107f1361..d55119b266 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -4,7 +4,6 @@ bool:wallet/wallet.cpp
float-divide-by-zero:policy/fees.cpp
float-divide-by-zero:validation.cpp
float-divide-by-zero:wallet/wallet.cpp
-nonnull-attribute:support/cleanse.cpp
unsigned-integer-overflow:arith_uint256.h
unsigned-integer-overflow:basic_string.h
unsigned-integer-overflow:bench/bench.h