Move external signer out of wallet module

This commit moves the ExternalSigner class and RPC methods out of the wallet module.

The enumeratesigners RPC can be used without a wallet since #21417.
With additional modifications external signers could be used without a wallet in general, e.g. via signrawtransaction.

The signerdisplayaddress RPC is ranamed to walletdisplayaddress because it requires wallet context.
A future displayaddress RPC call without wallet context could take a descriptor argument.

This commit fixes a rpc_help.py failure when configured with --disable-wallet.
This commit is contained in:
Sjors Provoost
2021-03-18 14:17:39 +01:00
parent 6664211be2
commit b54b2e7b1a
15 changed files with 160 additions and 134 deletions

79
test/functional/rpc_signer.py Executable file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# Copyright (c) 2017-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 external signer.
Verify that a bitcoind node can use an external signer command.
See also wallet_signer.py for tests that require wallet context.
"""
import os
import platform
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
class RPCSignerTest(BitcoinTestFramework):
def mock_signer_path(self):
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'signer.py')
if platform.system() == "Windows":
return "py " + path
else:
return path
def set_test_params(self):
self.num_nodes = 4
self.extra_args = [
[],
[f"-signer={self.mock_signer_path()}", '-keypool=10'],
[f"-signer={self.mock_signer_path()}", '-keypool=10'],
["-signer=fake.py"],
]
def skip_test_if_missing_module(self):
self.skip_if_no_external_signer()
def set_mock_result(self, node, res):
with open(os.path.join(node.cwd, "mock_result"), "w", encoding="utf8") as f:
f.write(res)
def clear_mock_result(self, node):
os.remove(os.path.join(node.cwd, "mock_result"))
def run_test(self):
self.log.debug(f"-signer={self.mock_signer_path()}")
assert_raises_rpc_error(-1, 'Error: restart bitcoind with -signer=<cmd>',
self.nodes[0].enumeratesigners
)
# Handle script missing:
assert_raises_rpc_error(-1, 'execve failed: No such file or directory',
self.nodes[3].enumeratesigners
)
# Handle error thrown by script
self.set_mock_result(self.nodes[1], "2")
assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
self.nodes[1].enumeratesigners
)
self.clear_mock_result(self.nodes[1])
self.set_mock_result(self.nodes[1], '0 [{"type": "trezor", "model": "trezor_t", "error": "fingerprint not found"}]')
assert_raises_rpc_error(-1, 'fingerprint not found',
self.nodes[1].enumeratesigners
)
self.clear_mock_result(self.nodes[1])
result = self.nodes[1].enumeratesigners()
assert_equal(len(result['signers']), 2)
assert_equal(result['signers'][0]["fingerprint"], "00000001")
assert_equal(result['signers'][0]["name"], "trezor_t")
if __name__ == '__main__':
RPCSignerTest().main()

View File

@@ -111,6 +111,7 @@ BASE_SCRIPTS = [
'wallet_listtransactions.py --legacy-wallet',
'wallet_listtransactions.py --descriptors',
'feature_taproot.py',
'rpc_signer.py',
'wallet_signer.py --descriptors',
# vv Tests less than 60s vv
'p2p_sendheaders.py',

View File

@@ -5,6 +5,7 @@
"""Test external signer.
Verify that a bitcoind node can use an external signer command
See also rpc_signer.py for tests without wallet context.
"""
import os
import platform
@@ -16,7 +17,7 @@ from test_framework.util import (
)
class SignerTest(BitcoinTestFramework):
class WalletSignerTest(BitcoinTestFramework):
def mock_signer_path(self):
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'signer.py')
if platform.system() == "Windows":
@@ -25,18 +26,16 @@ class SignerTest(BitcoinTestFramework):
return path
def set_test_params(self):
self.num_nodes = 4
self.num_nodes = 2
self.extra_args = [
[],
[f"-signer={self.mock_signer_path()}", '-keypool=10'],
[f"-signer={self.mock_signer_path()}", '-keypool=10'],
["-signer=fake.py"],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
self.skip_if_no_external_signer()
self.skip_if_no_wallet()
def set_mock_result(self, node, res):
with open(os.path.join(node.cwd, "mock_result"), "w", encoding="utf8") as f:
@@ -48,28 +47,6 @@ class SignerTest(BitcoinTestFramework):
def run_test(self):
self.log.debug(f"-signer={self.mock_signer_path()}")
assert_raises_rpc_error(-4, 'Error: restart bitcoind with -signer=<cmd>',
self.nodes[0].enumeratesigners
)
# Handle script missing:
assert_raises_rpc_error(-1, 'execve failed: No such file or directory',
self.nodes[3].enumeratesigners
)
# Handle error thrown by script
self.set_mock_result(self.nodes[1], "2")
assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
self.nodes[1].enumeratesigners
)
self.clear_mock_result(self.nodes[1])
self.set_mock_result(self.nodes[1], '0 [{"type": "trezor", "model": "trezor_t", "error": "fingerprint not found"}]')
assert_raises_rpc_error(-4, 'fingerprint not found',
self.nodes[1].enumeratesigners
)
self.clear_mock_result(self.nodes[1])
# Create new wallets for an external signer.
# disable_private_keys and descriptors must be true:
assert_raises_rpc_error(-4, "Private keys must be disabled when using an external signer", self.nodes[1].createwallet, wallet_name='not_hww', disable_private_keys=False, descriptors=True, external_signer=True)
@@ -81,11 +58,6 @@ class SignerTest(BitcoinTestFramework):
self.nodes[1].createwallet(wallet_name='hww', disable_private_keys=True, descriptors=True, external_signer=True)
hww = self.nodes[1].get_wallet_rpc('hww')
result = hww.enumeratesigners()
assert_equal(len(result['signers']), 2)
assert_equal(result['signers'][0]["fingerprint"], "00000001")
assert_equal(result['signers'][0]["name"], "trezor_t")
# Flag can't be set afterwards (could be added later for non-blank descriptor based watch-only wallets)
self.nodes[1].createwallet(wallet_name='not_hww', disable_private_keys=True, descriptors=True, external_signer=False)
not_hww = self.nodes[1].get_wallet_rpc('not_hww')
@@ -123,14 +95,14 @@ class SignerTest(BitcoinTestFramework):
assert_equal(address_info['ismine'], True)
assert_equal(address_info['hdkeypath'], "m/44'/1'/0'/0/0")
self.log.info('Test signerdisplayaddress')
result = hww.signerdisplayaddress(address1)
self.log.info('Test walletdisplayaddress')
result = hww.walletdisplayaddress(address1)
assert_equal(result, {"address": address1})
# Handle error thrown by script
self.set_mock_result(self.nodes[1], "2")
assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
hww.signerdisplayaddress, address1
hww.walletdisplayaddress, address1
)
self.clear_mock_result(self.nodes[1])
@@ -214,4 +186,4 @@ class SignerTest(BitcoinTestFramework):
# self.clear_mock_result(self.nodes[4])
if __name__ == '__main__':
SignerTest().main()
WalletSignerTest().main()