2019-01-31 17:07:45 +01:00
|
|
|
#!/usr/bin/env python3
|
2023-08-16 19:27:31 +02:00
|
|
|
# Copyright (c) 2018-2020 The Bitcoin Core developers
|
2019-01-31 17:07:45 +01:00
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
"""Test dash-wallet."""
|
2019-07-08 14:56:59 +02:00
|
|
|
|
|
|
|
import hashlib
|
|
|
|
import os
|
|
|
|
import stat
|
2019-01-31 17:07:45 +01:00
|
|
|
import subprocess
|
|
|
|
import textwrap
|
|
|
|
|
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
|
|
from test_framework.util import assert_equal
|
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
BUFFER_SIZE = 16 * 1024
|
|
|
|
|
2019-01-31 17:07:45 +01:00
|
|
|
class ToolWalletTest(BitcoinTestFramework):
|
|
|
|
def set_test_params(self):
|
|
|
|
self.num_nodes = 1
|
|
|
|
self.setup_clean_chain = True
|
2019-09-17 19:36:30 +02:00
|
|
|
self.rpc_timeout = 120
|
2019-01-31 17:07:45 +01:00
|
|
|
|
|
|
|
def skip_test_if_missing_module(self):
|
|
|
|
self.skip_if_no_wallet()
|
Merge #17497: test: skip tests when utils haven't been compiled
a67352161c68fea9764cc31aff199f112d8572c6 test: skip tool_wallet test when bitcoin-wallet isn't compiled (fanquake)
e9277baed64e1d4054a102e40b39a9aed7839c2f test: skip wallet_listreceivedby test when the cli isn't compiled (fanquake)
621d398750d9f5ce3e7ec75ccb160b3534dcc436 test: skip bitcoin_cli test when the cli isn't compiled (fanquake)
Pull request description:
Don't try and run the `interface_bitcoin_cli.py` test when `bitcoin-cli` isn't available.
```bash
stdout:
2019-11-17T01:51:41.623000Z TestFramework (INFO): Initializing test directory /var/folders/z2/cn877pxd3czdfh47mfkmbwgm0000gn/T/test_runner_₿_🏃_20191116_205141/interface_bitcoin_cli_0
2019-11-17T01:51:41.890000Z TestFramework (ERROR): Unexpected exception caught during testing
Traceback (most recent call last):
File "/Users/michael/github/bitcoin/test/functional/test_framework/test_framework.py", line 111, in main
self.run_test()
File "/Users/michael/github/bitcoin/test/functional/interface_bitcoin_cli.py", line 18, in run_test
cli_response = self.nodes[0].cli("-version").send_cli()
File "/Users/michael/github/bitcoin/test/functional/test_framework/test_node.py", line 528, in send_cli
process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
File "/Users/michael/.pyenv/versions/3.5.6/lib/python3.5/subprocess.py", line 676, in __init__
restore_signals, start_new_session)
File "/Users/michael/.pyenv/versions/3.5.6/lib/python3.5/subprocess.py", line 1289, in _execute_child
raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: '/Users/michael/github/bitcoin/src/bitcoin-cli'
```
Top commit has no ACKs.
Tree-SHA512: de27513a615d9d21271a0948e012c3209351e7374efd19bfa1bb9cda77e8fffe15d99e3424e4dbfa8cf826084f8af1670726f4703bd2b6093e7d37df4bea64f0
2019-11-19 16:11:27 +01:00
|
|
|
self.skip_if_no_wallet_tool()
|
2019-01-31 17:07:45 +01:00
|
|
|
|
|
|
|
def dash_wallet_process(self, *args):
|
|
|
|
binary = self.config["environment"]["BUILDDIR"] + '/src/dash-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.dash_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.dash_wallet_process(*args)
|
|
|
|
stdout, stderr = p.communicate()
|
|
|
|
assert_equal(stderr, '')
|
|
|
|
assert_equal(stdout, output)
|
2019-07-08 14:56:59 +02:00
|
|
|
assert_equal(p.poll(), 0)
|
2019-01-31 17:07:45 +01:00
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
def wallet_shasum(self):
|
|
|
|
h = hashlib.sha1()
|
|
|
|
mv = memoryview(bytearray(BUFFER_SIZE))
|
|
|
|
with open(self.wallet_path, 'rb', buffering=0) as f:
|
|
|
|
for n in iter(lambda : f.readinto(mv), 0):
|
|
|
|
h.update(mv[:n])
|
|
|
|
return h.hexdigest()
|
2019-01-31 17:07:45 +01:00
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
def wallet_timestamp(self):
|
|
|
|
return os.path.getmtime(self.wallet_path)
|
|
|
|
|
|
|
|
def wallet_permissions(self):
|
|
|
|
return oct(os.lstat(self.wallet_path).st_mode)[-3:]
|
|
|
|
|
|
|
|
def log_wallet_timestamp_comparison(self, old, new):
|
|
|
|
result = 'unchanged' if new == old else 'increased!'
|
|
|
|
self.log.debug('Wallet file timestamp {}'.format(result))
|
|
|
|
|
|
|
|
def test_invalid_tool_commands_and_args(self):
|
|
|
|
self.log.info('Testing that various invalid commands raise with specific error messages')
|
2019-01-31 17:07:45 +01:00
|
|
|
self.assert_raises_tool_error('Invalid command: foo', 'foo')
|
2019-07-08 14:56:59 +02:00
|
|
|
# `dash-wallet help` raises an error. Use `dash-wallet -help`.
|
2019-01-31 17:07:45 +01:00
|
|
|
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')
|
2023-07-05 20:27:28 +02:00
|
|
|
locked_dir = os.path.join(self.options.tmpdir, "node0", self.chain, "wallets")
|
2023-02-15 05:07:34 +01:00
|
|
|
error = "SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?"
|
|
|
|
if self.is_bdb_compiled():
|
|
|
|
error = 'Error initializing wallet database environment "{}"!'.format(locked_dir)
|
2022-03-03 20:34:50 +01:00
|
|
|
self.assert_raises_tool_error(
|
2023-02-15 05:07:34 +01:00
|
|
|
error,
|
2022-11-30 20:23:48 +01:00
|
|
|
'-wallet=' + self.default_wallet_name,
|
2022-03-03 20:34:50 +01:00
|
|
|
'info',
|
|
|
|
)
|
2023-07-05 20:27:28 +02:00
|
|
|
path = os.path.join(self.options.tmpdir, "node0", self.chain, "wallets", "nonexistent.dat")
|
2022-10-17 04:33:10 +02:00
|
|
|
self.assert_raises_tool_error("Failed to load database path '{}'. Path does not exist.".format(path), '-wallet=nonexistent.dat', 'info')
|
2019-01-31 17:07:45 +01:00
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
def test_tool_wallet_info(self):
|
|
|
|
# Stop the node to close the wallet to call the info command.
|
2019-01-31 17:07:45 +01:00
|
|
|
self.stop_node(0)
|
2019-07-08 14:56:59 +02:00
|
|
|
self.log.info('Calling wallet tool info, testing output')
|
|
|
|
#
|
|
|
|
# TODO: Wallet tool info should work with wallet file permissions set to
|
|
|
|
# read-only without raising:
|
|
|
|
# "Error loading wallet.dat. Is wallet being used by another process?"
|
|
|
|
# The following lines should be uncommented and the tests still succeed:
|
|
|
|
#
|
|
|
|
# self.log.debug('Setting wallet file permissions to 400 (read-only)')
|
|
|
|
# os.chmod(self.wallet_path, stat.S_IRUSR)
|
|
|
|
# assert(self.wallet_permissions() in ['400', '666']) # Sanity check. 666 because Appveyor.
|
|
|
|
# shasum_before = self.wallet_shasum()
|
|
|
|
timestamp_before = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
|
2019-01-31 17:07:45 +01:00
|
|
|
out = textwrap.dedent('''\
|
|
|
|
Wallet info
|
|
|
|
===========
|
|
|
|
Encrypted: no
|
|
|
|
HD (hd seed available): no
|
|
|
|
Keypool Size: 1
|
|
|
|
Transactions: 0
|
2022-05-01 21:42:51 +02:00
|
|
|
Address Book: 1
|
2019-01-31 17:07:45 +01:00
|
|
|
''')
|
2023-02-14 09:48:36 +01:00
|
|
|
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
|
2019-01-31 17:07:45 +01:00
|
|
|
|
|
|
|
self.start_node(0)
|
|
|
|
self.nodes[0].upgradetohd()
|
|
|
|
self.stop_node(0)
|
|
|
|
|
|
|
|
out = textwrap.dedent('''\
|
|
|
|
Wallet info
|
|
|
|
===========
|
|
|
|
Encrypted: no
|
|
|
|
HD (hd seed available): yes
|
|
|
|
Keypool Size: 2
|
|
|
|
Transactions: 0
|
2022-05-01 21:42:51 +02:00
|
|
|
Address Book: 1
|
2019-01-31 17:07:45 +01:00
|
|
|
''')
|
2022-11-30 20:23:48 +01:00
|
|
|
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
|
2019-07-08 14:56:59 +02:00
|
|
|
timestamp_after = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
|
|
|
|
self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
|
|
|
|
self.log.debug('Setting wallet file permissions back to 600 (read/write)')
|
|
|
|
os.chmod(self.wallet_path, stat.S_IRUSR | stat.S_IWUSR)
|
|
|
|
assert(self.wallet_permissions() in ['600', '666']) # Sanity check. 666 because Appveyor.
|
|
|
|
#
|
|
|
|
# TODO: Wallet tool info should not write to the wallet file.
|
|
|
|
# The following lines should be uncommented and the tests still succeed:
|
|
|
|
#
|
|
|
|
# assert_equal(timestamp_before, timestamp_after)
|
|
|
|
# shasum_after = self.wallet_shasum()
|
|
|
|
# assert_equal(shasum_before, shasum_after)
|
|
|
|
# self.log.debug('Wallet file shasum unchanged\n')
|
|
|
|
|
|
|
|
def test_tool_wallet_info_after_transaction(self):
|
|
|
|
"""
|
|
|
|
Mutate the wallet with a transaction to verify that the info command
|
|
|
|
output changes accordingly.
|
|
|
|
"""
|
2019-01-31 17:07:45 +01:00
|
|
|
self.start_node(0)
|
2019-07-08 14:56:59 +02:00
|
|
|
self.log.info('Generating transaction to mutate wallet')
|
2019-01-31 17:07:45 +01:00
|
|
|
self.nodes[0].generate(1)
|
|
|
|
self.stop_node(0)
|
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
self.log.info('Calling wallet tool info after generating a transaction, testing output')
|
|
|
|
shasum_before = self.wallet_shasum()
|
|
|
|
timestamp_before = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
|
2019-01-31 17:07:45 +01:00
|
|
|
out = textwrap.dedent('''\
|
|
|
|
Wallet info
|
|
|
|
===========
|
|
|
|
Encrypted: no
|
|
|
|
HD (hd seed available): yes
|
2022-05-01 21:42:51 +02:00
|
|
|
Keypool Size: 2
|
2019-01-31 17:07:45 +01:00
|
|
|
Transactions: 1
|
2022-05-01 21:42:51 +02:00
|
|
|
Address Book: 1
|
2019-01-31 17:07:45 +01:00
|
|
|
''')
|
2022-11-30 20:23:48 +01:00
|
|
|
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
|
2019-07-08 14:56:59 +02:00
|
|
|
shasum_after = self.wallet_shasum()
|
|
|
|
timestamp_after = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
|
|
|
|
self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
|
|
|
|
#
|
|
|
|
# TODO: Wallet tool info should not write to the wallet file.
|
|
|
|
# This assertion should be uncommented and succeed:
|
|
|
|
# assert_equal(timestamp_before, timestamp_after)
|
|
|
|
assert_equal(shasum_before, shasum_after)
|
|
|
|
self.log.debug('Wallet file shasum unchanged\n')
|
|
|
|
|
|
|
|
def test_tool_wallet_create_on_existing_wallet(self):
|
|
|
|
self.log.info('Calling wallet tool create on an existing wallet, testing output')
|
|
|
|
shasum_before = self.wallet_shasum()
|
|
|
|
timestamp_before = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp before calling create: {}'.format(timestamp_before))
|
2019-01-31 17:07:45 +01:00
|
|
|
out = textwrap.dedent('''\
|
|
|
|
Topping up keypool...
|
|
|
|
Wallet info
|
|
|
|
===========
|
|
|
|
Encrypted: no
|
|
|
|
HD (hd seed available): no
|
|
|
|
Keypool Size: 1000
|
|
|
|
Transactions: 0
|
|
|
|
Address Book: 0
|
|
|
|
''')
|
|
|
|
self.assert_tool_output(out, '-wallet=foo', 'create')
|
2019-07-08 14:56:59 +02:00
|
|
|
shasum_after = self.wallet_shasum()
|
|
|
|
timestamp_after = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp after calling create: {}'.format(timestamp_after))
|
|
|
|
self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
|
|
|
|
assert_equal(timestamp_before, timestamp_after)
|
|
|
|
assert_equal(shasum_before, shasum_after)
|
|
|
|
self.log.debug('Wallet file shasum unchanged\n')
|
|
|
|
|
|
|
|
def test_getwalletinfo_on_different_wallet(self):
|
|
|
|
self.log.info('Starting node with arg -wallet=foo')
|
2022-11-30 20:23:48 +01:00
|
|
|
self.start_node(0, ['-nowallet', '-wallet=foo'])
|
2019-07-08 14:56:59 +02:00
|
|
|
|
|
|
|
self.log.info('Calling getwalletinfo on a different wallet ("foo"), testing output')
|
|
|
|
shasum_before = self.wallet_shasum()
|
|
|
|
timestamp_before = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp before calling getwalletinfo: {}'.format(timestamp_before))
|
2019-01-31 17:07:45 +01:00
|
|
|
out = self.nodes[0].getwalletinfo()
|
|
|
|
self.stop_node(0)
|
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
shasum_after = self.wallet_shasum()
|
|
|
|
timestamp_after = self.wallet_timestamp()
|
|
|
|
self.log.debug('Wallet file timestamp after calling getwalletinfo: {}'.format(timestamp_after))
|
|
|
|
|
2019-01-31 17:07:45 +01:00
|
|
|
assert_equal(0, out['txcount'])
|
|
|
|
assert_equal(1000, out['keypoolsize'])
|
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
|
|
|
|
assert_equal(timestamp_before, timestamp_after)
|
|
|
|
assert_equal(shasum_after, shasum_before)
|
|
|
|
self.log.debug('Wallet file shasum unchanged\n')
|
|
|
|
|
2022-03-06 08:00:20 +01:00
|
|
|
def test_salvage(self):
|
|
|
|
# TODO: Check salvage actually salvages and doesn't break things. https://github.com/bitcoin/bitcoin/issues/7463
|
|
|
|
self.log.info('Check salvage')
|
2022-11-30 20:24:02 +01:00
|
|
|
self.start_node(0)
|
|
|
|
self.nodes[0].createwallet("salvage")
|
2022-03-06 08:00:20 +01:00
|
|
|
self.stop_node(0)
|
|
|
|
|
|
|
|
self.assert_tool_output('', '-wallet=salvage', 'salvage')
|
|
|
|
|
2023-06-27 20:51:40 +02:00
|
|
|
def test_wipe(self):
|
|
|
|
out = textwrap.dedent('''\
|
|
|
|
Wallet info
|
|
|
|
===========
|
|
|
|
Encrypted: no
|
|
|
|
HD (hd seed available): yes
|
|
|
|
Keypool Size: 2
|
|
|
|
Transactions: 1
|
|
|
|
Address Book: 1
|
|
|
|
''')
|
|
|
|
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
|
|
|
|
|
|
|
|
self.assert_tool_output('', '-wallet=' + self.default_wallet_name, 'wipetxes')
|
|
|
|
|
|
|
|
out = textwrap.dedent('''\
|
|
|
|
Wallet info
|
|
|
|
===========
|
|
|
|
Encrypted: no
|
|
|
|
HD (hd seed available): yes
|
|
|
|
Keypool Size: 2
|
|
|
|
Transactions: 0
|
|
|
|
Address Book: 1
|
|
|
|
''')
|
|
|
|
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
|
|
|
|
|
2019-07-08 14:56:59 +02:00
|
|
|
def run_test(self):
|
2022-11-30 20:23:48 +01:00
|
|
|
self.wallet_path = os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)
|
2019-07-08 14:56:59 +02:00
|
|
|
self.test_invalid_tool_commands_and_args()
|
|
|
|
# Warning: The following tests are order-dependent.
|
|
|
|
self.test_tool_wallet_info()
|
|
|
|
self.test_tool_wallet_info_after_transaction()
|
|
|
|
self.test_tool_wallet_create_on_existing_wallet()
|
|
|
|
self.test_getwalletinfo_on_different_wallet()
|
2023-02-12 11:51:09 +01:00
|
|
|
if self.is_bdb_compiled():
|
|
|
|
self.test_salvage()
|
2023-06-27 20:51:40 +02:00
|
|
|
self.test_wipe()
|
2019-07-08 14:56:59 +02:00
|
|
|
|
2019-01-31 17:07:45 +01:00
|
|
|
if __name__ == '__main__':
|
|
|
|
ToolWalletTest().main()
|