Merge pull request #8888 from SomberNight/202402_jsonrpc_errors
cli/rpc: nicer error messages and error-passing
This commit is contained in:
@@ -42,7 +42,8 @@ import os
|
||||
|
||||
from .import util, ecc
|
||||
from .util import (bfh, format_satoshis, json_decode, json_normalize,
|
||||
is_hash256_str, is_hex_str, to_bytes, parse_max_spend, to_decimal)
|
||||
is_hash256_str, is_hex_str, to_bytes, parse_max_spend, to_decimal,
|
||||
UserFacingException)
|
||||
from . import bitcoin
|
||||
from .bitcoin import is_address, hash_160, COIN
|
||||
from .bip32 import BIP32Node
|
||||
@@ -77,7 +78,7 @@ if TYPE_CHECKING:
|
||||
known_commands = {} # type: Dict[str, Command]
|
||||
|
||||
|
||||
class NotSynchronizedException(Exception):
|
||||
class NotSynchronizedException(UserFacingException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -144,21 +145,21 @@ def command(s):
|
||||
if isinstance(wallet, str):
|
||||
wallet = daemon.get_wallet(wallet)
|
||||
if wallet is None:
|
||||
raise Exception('wallet not loaded')
|
||||
raise UserFacingException('wallet not loaded')
|
||||
kwargs['wallet'] = wallet
|
||||
if cmd.requires_password and password is None and wallet.has_password():
|
||||
password = wallet.get_unlocked_password()
|
||||
if password:
|
||||
kwargs['password'] = password
|
||||
else:
|
||||
raise Exception('Password required. Unlock the wallet, or add a --password option to your command')
|
||||
raise UserFacingException('Password required. Unlock the wallet, or add a --password option to your command')
|
||||
wallet = kwargs.get('wallet') # type: Optional[Abstract_Wallet]
|
||||
if cmd.requires_wallet and not wallet:
|
||||
raise Exception('wallet not loaded')
|
||||
raise UserFacingException('wallet not loaded')
|
||||
if cmd.requires_password and password is None and wallet.has_password():
|
||||
raise Exception('Password required')
|
||||
raise UserFacingException('Password required')
|
||||
if cmd.requires_lightning and (not wallet or not wallet.has_lightning()):
|
||||
raise Exception('Lightning not enabled in this wallet')
|
||||
raise UserFacingException('Lightning not enabled in this wallet')
|
||||
return await func(*args, **kwargs)
|
||||
return func_wrapper
|
||||
return decorator
|
||||
@@ -251,7 +252,7 @@ class Commands:
|
||||
"""
|
||||
wallet = self.daemon.load_wallet(wallet_path, password, upgrade=True)
|
||||
if wallet is None:
|
||||
raise Exception('could not load wallet')
|
||||
raise UserFacingException('could not load wallet')
|
||||
if unlock:
|
||||
wallet.unlock(password)
|
||||
run_hook('load_wallet', wallet, None)
|
||||
@@ -301,7 +302,7 @@ class Commands:
|
||||
async def password(self, password=None, new_password=None, encrypt_file=None, wallet: Abstract_Wallet = None):
|
||||
"""Change wallet password. """
|
||||
if wallet.storage.is_encrypted_with_hw_device() and new_password:
|
||||
raise Exception("Can't change the password of a wallet encrypted with a hw device.")
|
||||
raise UserFacingException("Can't change the password of a wallet encrypted with a hw device.")
|
||||
if encrypt_file is None:
|
||||
if not password and new_password:
|
||||
# currently no password, setting one now: we encrypt by default
|
||||
@@ -403,18 +404,18 @@ class Commands:
|
||||
keypairs = {}
|
||||
inputs = [] # type: List[PartialTxInput]
|
||||
locktime = jsontx.get('locktime', 0)
|
||||
for txin_dict in jsontx.get('inputs'):
|
||||
for txin_idx, txin_dict in enumerate(jsontx.get('inputs')):
|
||||
if txin_dict.get('prevout_hash') is not None and txin_dict.get('prevout_n') is not None:
|
||||
prevout = TxOutpoint(txid=bfh(txin_dict['prevout_hash']), out_idx=int(txin_dict['prevout_n']))
|
||||
elif txin_dict.get('output'):
|
||||
prevout = TxOutpoint.from_str(txin_dict['output'])
|
||||
else:
|
||||
raise Exception("missing prevout for txin")
|
||||
raise UserFacingException(f"missing prevout for txin {txin_idx}")
|
||||
txin = PartialTxInput(prevout=prevout)
|
||||
try:
|
||||
txin._trusted_value_sats = int(txin_dict.get('value') or txin_dict['value_sats'])
|
||||
except KeyError:
|
||||
raise Exception("missing 'value_sats' field for txin")
|
||||
raise UserFacingException(f"missing 'value_sats' field for txin {txin_idx}")
|
||||
nsequence = txin_dict.get('nsequence', None)
|
||||
if nsequence is not None:
|
||||
txin.nsequence = nsequence
|
||||
@@ -428,15 +429,15 @@ class Commands:
|
||||
inputs.append(txin)
|
||||
|
||||
outputs = [] # type: List[PartialTxOutput]
|
||||
for txout_dict in jsontx.get('outputs'):
|
||||
for txout_idx, txout_dict in enumerate(jsontx.get('outputs')):
|
||||
try:
|
||||
txout_addr = txout_dict['address']
|
||||
except KeyError:
|
||||
raise Exception("missing 'address' field for txout")
|
||||
raise UserFacingException(f"missing 'address' field for txout {txout_idx}")
|
||||
try:
|
||||
txout_val = int(txout_dict.get('value') or txout_dict['value_sats'])
|
||||
except KeyError:
|
||||
raise Exception("missing 'value_sats' field for txout")
|
||||
raise UserFacingException(f"missing 'value_sats' field for txout {txout_idx}")
|
||||
txout = PartialTxOutput.from_address_and_value(txout_addr, txout_val)
|
||||
outputs.append(txout)
|
||||
|
||||
@@ -651,7 +652,7 @@ class Commands:
|
||||
try:
|
||||
node = BIP32Node.from_xkey(xkey)
|
||||
except Exception:
|
||||
raise Exception('xkey should be a master public/private key')
|
||||
raise UserFacingException('xkey should be a master public/private key')
|
||||
return node._replace(xtype=xtype).to_xkey()
|
||||
|
||||
@command('wp')
|
||||
@@ -672,12 +673,12 @@ class Commands:
|
||||
out = "Error: " + repr(e)
|
||||
return out
|
||||
|
||||
def _resolver(self, x, wallet):
|
||||
def _resolver(self, x, wallet: Abstract_Wallet):
|
||||
if x is None:
|
||||
return None
|
||||
out = wallet.contacts.resolve(x)
|
||||
if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
|
||||
raise Exception('cannot verify alias', x)
|
||||
raise UserFacingException(f"cannot verify alias: {x}")
|
||||
return out['address']
|
||||
|
||||
@command('n')
|
||||
@@ -802,13 +803,13 @@ class Commands:
|
||||
if is_hash256_str(tx): # txid
|
||||
tx = wallet.db.get_transaction(tx)
|
||||
if tx is None:
|
||||
raise Exception("Transaction not in wallet.")
|
||||
raise UserFacingException("Transaction not in wallet.")
|
||||
else: # raw tx
|
||||
try:
|
||||
tx = Transaction(tx)
|
||||
tx.deserialize()
|
||||
except transaction.SerializationError as e:
|
||||
raise Exception(f"Failed to deserialize transaction: {e}") from e
|
||||
raise UserFacingException(f"Failed to deserialize transaction: {e}") from e
|
||||
domain_coins = from_coins.split(',') if from_coins else None
|
||||
coins = wallet.get_spendable_coins(None)
|
||||
if domain_coins is not None:
|
||||
@@ -891,20 +892,20 @@ class Commands:
|
||||
if raw:
|
||||
tx = Transaction(raw)
|
||||
else:
|
||||
raise Exception("Unknown transaction")
|
||||
raise UserFacingException("Unknown transaction")
|
||||
if tx.txid() != txid:
|
||||
raise Exception("Mismatching txid")
|
||||
raise UserFacingException("Mismatching txid")
|
||||
return tx.serialize()
|
||||
|
||||
@command('')
|
||||
async def encrypt(self, pubkey, message) -> str:
|
||||
"""Encrypt a message with a public key. Use quotes if the message contains whitespaces."""
|
||||
if not is_hex_str(pubkey):
|
||||
raise Exception(f"pubkey must be a hex string instead of {repr(pubkey)}")
|
||||
raise UserFacingException(f"pubkey must be a hex string instead of {repr(pubkey)}")
|
||||
try:
|
||||
message = to_bytes(message)
|
||||
except TypeError:
|
||||
raise Exception(f"message must be a string-like object instead of {repr(message)}")
|
||||
raise UserFacingException(f"message must be a string-like object instead of {repr(message)}")
|
||||
public_key = ecc.ECPubkey(bfh(pubkey))
|
||||
encrypted = public_key.encrypt_message(message)
|
||||
return encrypted.decode('utf-8')
|
||||
@@ -913,9 +914,9 @@ class Commands:
|
||||
async def decrypt(self, pubkey, encrypted, password=None, wallet: Abstract_Wallet = None) -> str:
|
||||
"""Decrypt a message encrypted with a public key."""
|
||||
if not is_hex_str(pubkey):
|
||||
raise Exception(f"pubkey must be a hex string instead of {repr(pubkey)}")
|
||||
raise UserFacingException(f"pubkey must be a hex string instead of {repr(pubkey)}")
|
||||
if not isinstance(encrypted, (str, bytes, bytearray)):
|
||||
raise Exception(f"encrypted must be a string-like object instead of {repr(encrypted)}")
|
||||
raise UserFacingException(f"encrypted must be a string-like object instead of {repr(encrypted)}")
|
||||
decrypted = wallet.decrypt_message(pubkey, encrypted, password)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
@@ -924,7 +925,7 @@ class Commands:
|
||||
"""Returns a payment request"""
|
||||
r = wallet.get_request(request_id)
|
||||
if not r:
|
||||
raise Exception("Request not found")
|
||||
raise UserFacingException("Request not found")
|
||||
return wallet.export_request(r)
|
||||
|
||||
@command('w')
|
||||
@@ -932,7 +933,7 @@ class Commands:
|
||||
"""Returns an invoice (request for outgoing payment)"""
|
||||
r = wallet.get_invoice(invoice_id)
|
||||
if not r:
|
||||
raise Exception("Request not found")
|
||||
raise UserFacingException("Request not found")
|
||||
return wallet.export_invoice(r)
|
||||
|
||||
#@command('w')
|
||||
@@ -976,13 +977,14 @@ class Commands:
|
||||
async def changegaplimit(self, new_limit, iknowwhatimdoing=False, wallet: Abstract_Wallet = None):
|
||||
"""Change the gap limit of the wallet."""
|
||||
if not iknowwhatimdoing:
|
||||
raise Exception("WARNING: Are you SURE you want to change the gap limit?\n"
|
||||
"It makes recovering your wallet from seed difficult!\n"
|
||||
"Please do your research and make sure you understand the implications.\n"
|
||||
"Typically only merchants and power users might want to do this.\n"
|
||||
"To proceed, try again, with the --iknowwhatimdoing option.")
|
||||
raise UserFacingException(
|
||||
"WARNING: Are you SURE you want to change the gap limit?\n"
|
||||
"It makes recovering your wallet from seed difficult!\n"
|
||||
"Please do your research and make sure you understand the implications.\n"
|
||||
"Typically only merchants and power users might want to do this.\n"
|
||||
"To proceed, try again, with the --iknowwhatimdoing option.")
|
||||
if not isinstance(wallet, Deterministic_Wallet):
|
||||
raise Exception("This wallet is not deterministic.")
|
||||
raise UserFacingException("This wallet is not deterministic.")
|
||||
return wallet.change_gap_limit(new_limit)
|
||||
|
||||
@command('wn')
|
||||
@@ -991,7 +993,7 @@ class Commands:
|
||||
known addresses in the wallet.
|
||||
"""
|
||||
if not isinstance(wallet, Deterministic_Wallet):
|
||||
raise Exception("This wallet is not deterministic.")
|
||||
raise UserFacingException("This wallet is not deterministic.")
|
||||
if not wallet.is_up_to_date():
|
||||
raise NotSynchronizedException("Wallet not fully synchronized.")
|
||||
return wallet.min_acceptable_gap()
|
||||
@@ -1091,11 +1093,12 @@ class Commands:
|
||||
transactions.
|
||||
"""
|
||||
if not is_hash256_str(txid):
|
||||
raise Exception(f"{repr(txid)} is not a txid")
|
||||
raise UserFacingException(f"{repr(txid)} is not a txid")
|
||||
height = wallet.adb.get_tx_height(txid).height
|
||||
if height != TX_HEIGHT_LOCAL:
|
||||
raise Exception(f'Only local transactions can be removed. '
|
||||
f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
|
||||
raise UserFacingException(
|
||||
f'Only local transactions can be removed. '
|
||||
f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
|
||||
wallet.adb.remove_transaction(txid)
|
||||
wallet.save_db()
|
||||
|
||||
@@ -1105,9 +1108,9 @@ class Commands:
|
||||
The transaction must be related to the wallet.
|
||||
"""
|
||||
if not is_hash256_str(txid):
|
||||
raise Exception(f"{repr(txid)} is not a txid")
|
||||
raise UserFacingException(f"{repr(txid)} is not a txid")
|
||||
if not wallet.db.get_transaction(txid):
|
||||
raise Exception("Transaction not in wallet.")
|
||||
raise UserFacingException("Transaction not in wallet.")
|
||||
return {
|
||||
"confirmations": wallet.adb.get_tx_height(txid).conf,
|
||||
}
|
||||
@@ -1253,8 +1256,9 @@ class Commands:
|
||||
async def get_channel_ctx(self, channel_point, iknowwhatimdoing=False, wallet: Abstract_Wallet = None):
|
||||
""" return the current commitment transaction of a channel """
|
||||
if not iknowwhatimdoing:
|
||||
raise Exception("WARNING: this command is potentially unsafe.\n"
|
||||
"To proceed, try again, with the --iknowwhatimdoing option.")
|
||||
raise UserFacingException(
|
||||
"WARNING: this command is potentially unsafe.\n"
|
||||
"To proceed, try again, with the --iknowwhatimdoing option.")
|
||||
txid, index = channel_point.split(':')
|
||||
chan_id, _ = channel_id_from_funding_tx(txid, int(index))
|
||||
chan = wallet.lnworker.channels[chan_id]
|
||||
@@ -1354,7 +1358,7 @@ class Commands:
|
||||
configured exchange rate source.
|
||||
"""
|
||||
if not self.daemon.fx.is_enabled():
|
||||
raise Exception("FX is disabled. To enable, run: 'electrum setconfig use_exchange_rate true'")
|
||||
raise UserFacingException("FX is disabled. To enable, run: 'electrum setconfig use_exchange_rate true'")
|
||||
# Currency codes are uppercase
|
||||
from_ccy = from_ccy.upper()
|
||||
to_ccy = to_ccy.upper()
|
||||
@@ -1368,9 +1372,9 @@ class Commands:
|
||||
rate_to = self.daemon.fx.exchange.get_cached_spot_quote(to_ccy)
|
||||
# Test if currencies exist
|
||||
if rate_from.is_nan():
|
||||
raise Exception(f'Currency to convert from ({from_ccy}) is unknown or rate is unavailable')
|
||||
raise UserFacingException(f'Currency to convert from ({from_ccy}) is unknown or rate is unavailable')
|
||||
if rate_to.is_nan():
|
||||
raise Exception(f'Currency to convert to ({to_ccy}) is unknown or rate is unavailable')
|
||||
raise UserFacingException(f'Currency to convert to ({to_ccy}) is unknown or rate is unavailable')
|
||||
# Conversion
|
||||
try:
|
||||
from_amount = to_decimal(from_amount)
|
||||
|
||||
@@ -35,6 +35,7 @@ from base64 import b64decode, b64encode
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import socket
|
||||
from enum import IntEnum
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import web, client_exceptions
|
||||
@@ -44,7 +45,7 @@ from . import util
|
||||
from .network import Network
|
||||
from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare, InvalidPassword)
|
||||
from .invoices import PR_PAID, PR_EXPIRED
|
||||
from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup
|
||||
from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup, UserFacingException, JsonRPCError
|
||||
from .util import EventListener, event_listener
|
||||
from .wallet import Wallet, Abstract_Wallet
|
||||
from .storage import WalletStorage
|
||||
@@ -251,11 +252,20 @@ class AuthenticatedServer(Logger):
|
||||
response['result'] = await f(**params)
|
||||
else:
|
||||
response['result'] = await f(*params)
|
||||
except UserFacingException as e:
|
||||
response['error'] = {
|
||||
'code': JsonRPCError.Codes.USERFACING,
|
||||
'message': str(e),
|
||||
}
|
||||
except BaseException as e:
|
||||
self.logger.exception("internal error while executing RPC")
|
||||
response['error'] = {
|
||||
'code': 1,
|
||||
'message': str(e),
|
||||
'code': JsonRPCError.Codes.INTERNAL,
|
||||
'message': "internal error while executing RPC",
|
||||
'data': {
|
||||
"exception": repr(e),
|
||||
"traceback": "".join(traceback.format_exception(e)),
|
||||
},
|
||||
}
|
||||
return web.json_response(response)
|
||||
|
||||
@@ -325,12 +335,11 @@ class CommandsServer(AuthenticatedServer):
|
||||
if hasattr(self.daemon.gui_object, 'new_window'):
|
||||
path = config_options.get('wallet_path') or self.config.get_wallet_path(use_gui_last_wallet=True)
|
||||
self.daemon.gui_object.new_window(path, config_options.get('url'))
|
||||
response = "ok"
|
||||
return True
|
||||
else:
|
||||
response = "error: current GUI does not support multiple windows"
|
||||
raise UserFacingException("error: current GUI does not support multiple windows")
|
||||
else:
|
||||
response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
|
||||
return response
|
||||
raise UserFacingException("error: Electrum is running in daemon mode. Please stop the daemon first.")
|
||||
|
||||
async def run_cmdline(self, config_options):
|
||||
cmdname = config_options['cmd']
|
||||
@@ -348,11 +357,8 @@ class CommandsServer(AuthenticatedServer):
|
||||
elif 'wallet' in cmd.options:
|
||||
kwargs['wallet'] = config_options.get('wallet_path')
|
||||
func = getattr(self.cmd_runner, cmd.name)
|
||||
# fixme: not sure how to retrieve message in jsonrpcclient
|
||||
try:
|
||||
result = await func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
result = {'error':str(e)}
|
||||
# execute requested command now. note: cmd can raise, the caller (self.handle) will wrap it.
|
||||
result = await func(*args, **kwargs)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import functools
|
||||
from functools import partial
|
||||
from abc import abstractmethod, ABC
|
||||
import socket
|
||||
import enum
|
||||
|
||||
import attr
|
||||
import aiohttp
|
||||
@@ -1932,6 +1933,20 @@ class MySocksProxy(aiorpcx.SOCKSProxy):
|
||||
return ret
|
||||
|
||||
|
||||
class JsonRPCError(Exception):
|
||||
|
||||
class Codes(enum.IntEnum):
|
||||
# application-specific error codes
|
||||
USERFACING = 1
|
||||
INTERNAL = 2
|
||||
|
||||
def __init__(self, *, code: int, message: str, data: Optional[dict] = None):
|
||||
Exception.__init__(self)
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.data = data
|
||||
|
||||
|
||||
class JsonRPCClient:
|
||||
|
||||
def __init__(self, session: aiohttp.ClientSession, url: str):
|
||||
@@ -1940,6 +1955,10 @@ class JsonRPCClient:
|
||||
self._id = 0
|
||||
|
||||
async def request(self, endpoint, *args):
|
||||
"""Send request to server, parse and return result.
|
||||
note: parsing code is naive, the server is assumed to be well-behaved.
|
||||
Up to the caller to handle exceptions, including those arising from parsing errors.
|
||||
"""
|
||||
self._id += 1
|
||||
data = ('{"jsonrpc": "2.0", "id":"%d", "method": "%s", "params": %s }'
|
||||
% (self._id, endpoint, json.dumps(args)))
|
||||
@@ -1949,7 +1968,7 @@ class JsonRPCClient:
|
||||
result = r.get('result')
|
||||
error = r.get('error')
|
||||
if error:
|
||||
return 'Error: ' + str(error)
|
||||
raise JsonRPCError(code=error["code"], message=error["message"], data=error.get("data"))
|
||||
else:
|
||||
return result
|
||||
else:
|
||||
|
||||
@@ -817,7 +817,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
return serialized_privkey
|
||||
|
||||
def export_private_key_for_path(self, path: Union[Sequence[int], str], password: Optional[str]) -> str:
|
||||
raise Exception("this wallet is not deterministic")
|
||||
raise UserFacingException("this wallet is not deterministic")
|
||||
|
||||
@abstractmethod
|
||||
def get_public_keys(self, address: str) -> Sequence[str]:
|
||||
@@ -1440,7 +1440,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
# FIXME: Lightning capital gains would requires FIFO
|
||||
if (from_timestamp is not None or to_timestamp is not None) \
|
||||
and (from_height is not None or to_height is not None):
|
||||
raise Exception('timestamp and block height based filtering cannot be used together')
|
||||
raise UserFacingException('timestamp and block height based filtering cannot be used together')
|
||||
|
||||
show_fiat = fx and fx.is_enabled() and fx.has_history()
|
||||
out = []
|
||||
@@ -2597,17 +2597,17 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
return choice
|
||||
|
||||
def create_new_address(self, for_change: bool = False):
|
||||
raise Exception("this wallet cannot generate new addresses")
|
||||
raise UserFacingException("this wallet cannot generate new addresses")
|
||||
|
||||
def import_address(self, address: str) -> str:
|
||||
raise Exception("this wallet cannot import addresses")
|
||||
raise UserFacingException("this wallet cannot import addresses")
|
||||
|
||||
def import_addresses(self, addresses: List[str], *,
|
||||
write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]:
|
||||
raise Exception("this wallet cannot import addresses")
|
||||
raise UserFacingException("this wallet cannot import addresses")
|
||||
|
||||
def delete_address(self, address: str) -> None:
|
||||
raise Exception("this wallet cannot delete addresses")
|
||||
raise UserFacingException("this wallet cannot delete addresses")
|
||||
|
||||
def get_request_URI(self, req: Request) -> Optional[str]:
|
||||
lightning_invoice = None
|
||||
@@ -3056,7 +3056,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
) -> PartialTransaction:
|
||||
"""Helper function for make_unsigned_transaction."""
|
||||
if fee is not None and feerate is not None:
|
||||
raise Exception("Cannot specify both 'fee' and 'feerate' at the same time!")
|
||||
raise UserFacingException("Cannot specify both 'fee' and 'feerate' at the same time!")
|
||||
coins = self.get_spendable_coins(domain_addr, nonlocal_only=nonlocal_only)
|
||||
if domain_coins is not None:
|
||||
coins = [coin for coin in coins if (coin.prevout.to_str() in domain_coins)]
|
||||
@@ -3905,7 +3905,7 @@ def create_new_wallet(*, path, config: SimpleConfig, passphrase=None, password=N
|
||||
"""Create a new wallet"""
|
||||
storage = WalletStorage(path)
|
||||
if storage.file_exists():
|
||||
raise Exception("Remove the existing wallet first!")
|
||||
raise UserFacingException("Remove the existing wallet first!")
|
||||
db = WalletDB('', storage=storage, upgrade=True)
|
||||
|
||||
seed = Mnemonic('en').make_seed(seed_type=seed_type)
|
||||
@@ -3942,7 +3942,7 @@ def restore_wallet_from_text(
|
||||
else:
|
||||
storage = WalletStorage(path)
|
||||
if storage.file_exists():
|
||||
raise Exception("Remove the existing wallet first!")
|
||||
raise UserFacingException("Remove the existing wallet first!")
|
||||
if encrypt_file is None:
|
||||
encrypt_file = True
|
||||
db = WalletDB('', storage=storage, upgrade=True)
|
||||
@@ -3953,7 +3953,7 @@ def restore_wallet_from_text(
|
||||
good_inputs, bad_inputs = wallet.import_addresses(addresses, write_to_disk=False)
|
||||
# FIXME tell user about bad_inputs
|
||||
if not good_inputs:
|
||||
raise Exception("None of the given addresses can be imported")
|
||||
raise UserFacingException("None of the given addresses can be imported")
|
||||
elif keystore.is_private_key_list(text, allow_spaces_inside_key=False):
|
||||
k = keystore.Imported_KeyStore({})
|
||||
db.put('keystore', k.dump())
|
||||
@@ -3962,7 +3962,7 @@ def restore_wallet_from_text(
|
||||
good_inputs, bad_inputs = wallet.import_private_keys(keys, None, write_to_disk=False)
|
||||
# FIXME tell user about bad_inputs
|
||||
if not good_inputs:
|
||||
raise Exception("None of the given privkeys can be imported")
|
||||
raise UserFacingException("None of the given privkeys can be imported")
|
||||
else:
|
||||
if keystore.is_master_key(text):
|
||||
k = keystore.from_master_key(text)
|
||||
@@ -3971,7 +3971,7 @@ def restore_wallet_from_text(
|
||||
if k.can_have_deterministic_lightning_xprv():
|
||||
db.put('lightning_xprv', k.get_lightning_xprv(None))
|
||||
else:
|
||||
raise Exception("Seed or key not recognized")
|
||||
raise UserFacingException("Seed or key not recognized")
|
||||
db.put('keystore', k.dump())
|
||||
db.put('wallet_type', 'standard')
|
||||
if gap_limit is not None:
|
||||
|
||||
36
run_electrum
36
run_electrum
@@ -104,7 +104,7 @@ from electrum.util import InvalidPassword
|
||||
from electrum.commands import get_parser, known_commands, Commands, config_variables
|
||||
from electrum import daemon
|
||||
from electrum import keystore
|
||||
from electrum.util import create_and_start_event_loop
|
||||
from electrum.util import create_and_start_event_loop, UserFacingException, JsonRPCError
|
||||
from electrum.i18n import set_language
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -462,7 +462,17 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
||||
else:
|
||||
sys_exit(0)
|
||||
else:
|
||||
result = daemon.request(config, 'gui', (config_options,))
|
||||
try:
|
||||
result = daemon.request(config, 'gui', (config_options,))
|
||||
except JsonRPCError as e:
|
||||
if e.code == JsonRPCError.Codes.USERFACING:
|
||||
print_stderr(e.message)
|
||||
elif e.code == JsonRPCError.Codes.INTERNAL:
|
||||
print_stderr("(inside daemon): " + e.data["traceback"])
|
||||
print_stderr(e.message)
|
||||
else:
|
||||
raise Exception(f"unknown error code {e.code}")
|
||||
sys_exit(1)
|
||||
|
||||
elif cmdname == 'daemon':
|
||||
|
||||
@@ -497,8 +507,17 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
||||
print_msg("Found lingering lockfile for daemon. Removing.")
|
||||
daemon.remove_lockfile(lockfile)
|
||||
sys_exit(1)
|
||||
except JsonRPCError as e:
|
||||
if e.code == JsonRPCError.Codes.USERFACING:
|
||||
print_stderr(e.message)
|
||||
elif e.code == JsonRPCError.Codes.INTERNAL:
|
||||
print_stderr("(inside daemon): " + e.data["traceback"])
|
||||
print_stderr(e.message)
|
||||
else:
|
||||
raise Exception(f"unknown error code {e.code}")
|
||||
sys_exit(1)
|
||||
except Exception as e:
|
||||
print_stderr(str(e) or repr(e))
|
||||
_logger.exception("error running command (with daemon)")
|
||||
sys_exit(1)
|
||||
else:
|
||||
if cmd.requires_network:
|
||||
@@ -520,14 +539,15 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
|
||||
finally:
|
||||
plugins.stop()
|
||||
plugins.stopped_event.wait(1)
|
||||
except Exception as e:
|
||||
print_stderr(str(e) or repr(e))
|
||||
except UserFacingException as e:
|
||||
print_stderr(str(e))
|
||||
sys_exit(1)
|
||||
except Exception as e:
|
||||
_logger.exception("error running command (without daemon)")
|
||||
sys_exit(1)
|
||||
# print result
|
||||
if isinstance(result, str):
|
||||
print_msg(result)
|
||||
elif type(result) is dict and result.get('error'):
|
||||
print_stderr(result.get('error'))
|
||||
sys_exit(1)
|
||||
elif result is not None:
|
||||
print_msg(json_encode(result))
|
||||
sys_exit(0)
|
||||
|
||||
Reference in New Issue
Block a user