From 9cfeadea706dbbb46b1a4ab7b38ec11a4f527669 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 19 Aug 2019 12:46:31 +0200 Subject: [PATCH] Turn daemon subcommands into RPCs --- electrum/commands.py | 61 +++++++++++++++++++++++- electrum/daemon.py | 47 ++----------------- electrum/tests/regtest/regtest.sh | 77 ++++++++++++++++--------------- run_electrum | 8 ++-- 4 files changed, 106 insertions(+), 87 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 637b64aaf..b29691977 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -50,6 +50,9 @@ from .address_synchronizer import TX_HEIGHT_LOCAL from .mnemonic import Mnemonic from .lnutil import SENT, RECEIVED from .lnpeer import channel_id_from_funding_tx +from .plugin import run_hook +from .version import ELECTRUM_VERSION + if TYPE_CHECKING: from .network import Network @@ -105,10 +108,12 @@ def command(s): class Commands: - def __init__(self, config: 'SimpleConfig', wallet: Abstract_Wallet, - network: Optional['Network'], callback=None): + def __init__(self, config: 'SimpleConfig', + wallet: Abstract_Wallet, + network: Optional['Network'], daemon=None, callback=None): self.config = config self.wallet = wallet + self.daemon = daemon self.network = network self._callback = callback self.lnworker = self.wallet.lnworker if self.wallet else None @@ -140,6 +145,58 @@ class Commands: """List of commands""" return ' '.join(sorted(known_commands.keys())) + @command('n') + async def getinfo(self): + """ network info """ + net_params = self.network.get_parameters() + response = { + 'path': self.network.config.path, + 'server': net_params.host, + 'blockchain_height': self.network.get_local_height(), + 'server_height': self.network.get_server_height(), + 'spv_nodes': len(self.network.get_interfaces()), + 'connected': self.network.is_connected(), + 'auto_connect': net_params.auto_connect, + 'version': ELECTRUM_VERSION, + 'default_wallet': self.config.get_wallet_path(), + 'fee_per_kb': self.config.fee_per_kb(), + } + return response + + @command('n') + async def stop(self): + """Stop daemon""" + self.daemon.stop() + return "Daemon stopped" + + @command('n') + async def list_wallets(self): + """List wallets open in daemon""" + return [{'path':k, 'synchronized':w.is_up_to_date()} for k, w in self.daemon.wallets.items()] + + @command('n') + async def load_wallet(self): + """Open wallet in daemon""" + path = self.config.get_wallet_path() + wallet = self.daemon.load_wallet(path, self.config.get('password')) + if wallet is not None: + self.wallet = wallet + run_hook('load_wallet', wallet, None) + response = wallet is not None + return response + + @command('n') + async def close_wallet(self): + """Close wallet""" + path = self.config.get_wallet_path() + path = standardize_path(path) + if path in self.wallets: + self.stop_wallet(path) + response = True + else: + response = False + return response + @command('') async def create(self, passphrase=None, password=None, encrypt_file=True, seed_type=None): """Create a new wallet. diff --git a/electrum/daemon.py b/electrum/daemon.py index 46d8e0702..6c30a1502 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -38,7 +38,6 @@ import jsonrpcclient import jsonrpcserver from jsonrpcclient.clients.aiohttp_client import AiohttpClient -from .version import ELECTRUM_VERSION from .network import Network from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare) from .wallet import Wallet, Abstract_Wallet @@ -46,7 +45,6 @@ from .storage import WalletStorage from .commands import known_commands, Commands from .simple_config import SimpleConfig from .exchange_rate import FxThread -from .plugin import run_hook from .logging import get_logger, Logger @@ -243,7 +241,7 @@ class Daemon(Logger): self.methods.add(self.ping) self.methods.add(self.gui) self.methods.add(self.daemon) - self.cmd_runner = Commands(self.config, None, self.network) + self.cmd_runner = Commands(self.config, None, self.network, self) for cmdname in known_commands: self.methods.add(getattr(self.cmd_runner, cmdname)) self.methods.add(self.run_cmdline) @@ -263,46 +261,9 @@ class Daemon(Logger): async def daemon(self, config_options): config = SimpleConfig(config_options) sub = config.get('subcommand') - assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet'] + assert sub in [None, 'start', 'stop'] if sub in [None, 'start']: response = "Daemon already running" - elif sub == 'load_wallet': - path = config.get_wallet_path() - wallet = self.load_wallet(path, config.get('password')) - if wallet is not None: - self.cmd_runner.wallet = wallet - run_hook('load_wallet', wallet, None) - response = wallet is not None - elif sub == 'close_wallet': - path = config.get_wallet_path() - path = standardize_path(path) - if path in self.wallets: - self.stop_wallet(path) - response = True - else: - response = False - elif sub == 'status': - if self.network: - net_params = self.network.get_parameters() - current_wallet = self.cmd_runner.wallet - current_wallet_path = current_wallet.storage.path \ - if current_wallet else None - response = { - 'path': self.network.config.path, - 'server': net_params.host, - 'blockchain_height': self.network.get_local_height(), - 'server_height': self.network.get_server_height(), - 'spv_nodes': len(self.network.get_interfaces()), - 'connected': self.network.is_connected(), - 'auto_connect': net_params.auto_connect, - 'version': ELECTRUM_VERSION, - 'wallets': {k: w.is_up_to_date() - for k, w in self.wallets.items()}, - 'current_wallet': current_wallet_path, - 'fee_per_kb': self.config.fee_per_kb(), - } - else: - response = "Daemon offline" elif sub == 'stop': self.stop() response = "Daemon stopped" @@ -382,7 +343,7 @@ class Daemon(Logger): path = standardize_path(path) wallet = self.wallets.get(path) if wallet is None: - return {'error': 'Wallet "%s" is not loaded. Use "electrum daemon load_wallet"'%os.path.basename(path) } + return {'error': 'Wallet "%s" is not loaded. Use "electrum load_wallet"'%os.path.basename(path) } else: wallet = None # arguments passed to function @@ -393,7 +354,7 @@ class Daemon(Logger): kwargs = {} for x in cmd.options: kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x)) - cmd_runner = Commands(config, wallet, self.network) + cmd_runner = Commands(config, wallet, self.network, self) func = getattr(cmd_runner, cmd.name) try: result = await func(*args, **kwargs) diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh index a665af3c9..12f856404 100755 --- a/electrum/tests/regtest/regtest.sh +++ b/electrum/tests/regtest/regtest.sh @@ -43,8 +43,11 @@ if [[ $1 == "init" ]]; then $bob create > /dev/null $carol create > /dev/null $alice setconfig log_to_file True - $bob setconfig log_to_file True + $bob setconfig log_to_file True $carol setconfig log_to_file True + $alice setconfig server 127.0.0.1:51001:t + $bob setconfig server 127.0.0.1:51001:t + $carol setconfig server 127.0.0.1:51001:t $bob setconfig lightning_listen localhost:9735 $bob setconfig lightning_forward_payments true echo "funding alice and carol" @@ -55,20 +58,20 @@ fi # start daemons. Bob is started first because he is listening if [[ $1 == "start" ]]; then - $bob daemon -s 127.0.0.1:51001:t start - $alice daemon -s 127.0.0.1:51001:t start - $carol daemon -s 127.0.0.1:51001:t start + $bob daemon start + $alice daemon start + $carol daemon start sleep 1 # time to accept commands - $bob daemon load_wallet - $alice daemon load_wallet - $carol daemon load_wallet + $bob load_wallet + $alice load_wallet + $carol load_wallet sleep 10 # give time to synchronize fi if [[ $1 == "stop" ]]; then - $alice daemon stop || true - $bob daemon stop || true - $carol daemon stop || true + $alice stop || true + $bob stop || true + $carol stop || true fi if [[ $1 == "open" ]]; then @@ -129,10 +132,10 @@ if [[ $1 == "breach" ]]; then fi if [[ $1 == "redeem_htlcs" ]]; then - $bob daemon stop - ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=10 $bob daemon -s 127.0.0.1:51001:t start + $bob stop + ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=10 $bob daemon start sleep 1 - $bob daemon load_wallet + $bob load_wallet sleep 1 # alice opens channel bob_node=$($bob nodeid) @@ -149,7 +152,7 @@ if [[ $1 == "redeem_htlcs" ]]; then exit 1 fi # bob goes away - $bob daemon stop + $bob stop echo "alice balance before closing channel:" $($alice getbalance) balance_before=$($alice getbalance | jq '[.confirmed, .unconfirmed, .lightning] | to_entries | map(select(.value != null).value) | map(tonumber) | add ') # alice force closes the channel @@ -177,10 +180,10 @@ fi if [[ $1 == "breach_with_unspent_htlc" ]]; then - $bob daemon stop - ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=3 $bob daemon -s 127.0.0.1:51001:t start + $bob stop + ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=3 $bob daemon start sleep 1 - $bob daemon load_wallet + $bob load_wallet wait_until_funded echo "alice opens channel" bob_node=$($bob nodeid) @@ -205,24 +208,24 @@ if [[ $1 == "breach_with_unspent_htlc" ]]; then echo $($bob getbalance) echo "alice breaches with old ctx" echo $ctx - height1=$($bob daemon status | jq '.blockchain_height') + height1=$($bob getinfo | jq '.blockchain_height') $bitcoin_cli sendrawtransaction $ctx new_blocks 1 # wait until breach is confirmed - while height2=$($bob daemon status | jq '.blockchain_height') && [ $(($height2 - $height1)) -ne 1 ]; do + while height2=$($bob getinfo | jq '.blockchain_height') && [ $(($height2 - $height1)) -ne 1 ]; do echo "waiting for block" sleep 1 done new_blocks 1 # wait until next block is confirmed, so that htlc tx and redeem tx are confirmed too - while height3=$($bob daemon status | jq '.blockchain_height') && [ $(($height3 - $height2)) -ne 1 ]; do + while height3=$($bob getinfo | jq '.blockchain_height') && [ $(($height3 - $height2)) -ne 1 ]; do echo "waiting for block" sleep 1 done # wait until wallet is synchronized - while b=$($bob daemon status | jq '.wallets | ."/tmp/bob/regtest/wallets/default_wallet"') && [ "$b" != "true" ]; do - echo "waiting for wallet sync $b" - sleep 1 + while b=$($bob list_wallets | jq '.[0]|.synchronized') && [ "$b" != "true" ]; do + echo "waiting for wallet sync: $b" + sleep 1 done echo $($bob getbalance) balance_after=$($bob getbalance | jq '[.confirmed, .unconfirmed] | to_entries | map(select(.value != null).value) | map(tonumber) | add ') @@ -234,10 +237,10 @@ fi if [[ $1 == "breach_with_spent_htlc" ]]; then - $bob daemon stop - ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=3 $bob daemon -s 127.0.0.1:51001:t start + $bob stop + ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=3 $bob daemon start sleep 1 - $bob daemon load_wallet + $bob load_wallet wait_until_funded echo "alice opens channel" bob_node=$($bob nodeid) @@ -262,7 +265,7 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then fi echo $($bob getbalance) echo "bob goes offline" - $bob daemon stop + $bob stop ctx_id=$($bitcoin_cli sendrawtransaction $ctx) echo "alice breaches with old ctx:" $ctx_id new_blocks 1 @@ -275,11 +278,11 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then # (to_local needs to_self_delay blocks; htlc needs whatever we put in invoice) new_blocks 150 echo "alice spends to_local and htlc outputs" - $alice daemon stop + $alice stop cp /tmp/alice/regtest/wallets/toxic_wallet /tmp/alice/regtest/wallets/default_wallet - $alice daemon -s 127.0.0.1:51001:t start + $alice daemon start sleep 1 - $alice daemon load_wallet + $alice load_wallet # wait until alice has spent both ctx outputs while [[ $($bitcoin_cli gettxout $ctx_id 0) ]]; do echo "waiting until alice spends ctx outputs" @@ -291,9 +294,9 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then done new_blocks 1 echo "bob comes back" - $bob daemon -s 127.0.0.1:51001:t start + $bob daemon start sleep 1 - $bob daemon load_wallet + $bob load_wallet while [[ $($bitcoin_cli getmempoolinfo | jq '.size') != "1" ]]; do echo "waiting for bob's transaction" sleep 1 @@ -311,15 +314,15 @@ fi if [[ $1 == "watchtower" ]]; then # carol is a watchtower of alice - $alice daemon stop - $carol daemon stop + $alice stop + $carol stop $alice setconfig watchtower_url http://127.0.0.1:12345 $carol setconfig watchtower_host 127.0.0.1 $carol setconfig watchtower_port 12345 - $carol daemon -s 127.0.0.1:51001:t start - $alice daemon -s 127.0.0.1:51001:t start + $carol daemon start + $alice daemon start sleep 1 - $alice daemon load_wallet + $alice load_wallet echo "waiting until alice funded" wait_until_funded echo "alice opens channel" diff --git a/run_electrum b/run_electrum index bc1c50802..8647552a3 100755 --- a/run_electrum +++ b/run_electrum @@ -159,8 +159,9 @@ def init_cmdline(config_options, server): print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.") # commands needing password - if (cmd.requires_wallet and storage.is_encrypted() and server is False)\ - or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())): + if ( (cmd.requires_wallet and storage.is_encrypted() and server is False)\ + or (cmdname == 'load_wallet' and storage.is_encrypted())\ + or (cmd.requires_password and (storage.is_encrypted() or storage.get('use_encryption')))): if storage.is_encrypted_with_hw_device(): # this case is handled later in the control flow password = None @@ -392,9 +393,6 @@ if __name__ == '__main__': elif cmdname == 'daemon': - if subcommand in ['load_wallet']: - init_daemon(config_options) - if subcommand in [None, 'start']: configure_logging(config) fd = daemon.get_file_descriptor(config)