separation between RPC and non-RPC commands.

This commit is contained in:
ThomasV 2015-12-23 10:54:31 +01:00
parent 62201b37f5
commit acbe67fd1f
3 changed files with 144 additions and 129 deletions

234
electrum
View File

@ -107,107 +107,47 @@ def init_gui(config, network, plugins):
return gui return gui
def run_non_RPC(config):
def init_cmdline(config):
cmdname = config.get('cmd') cmdname = config.get('cmd')
cmd = known_commands[cmdname]
if cmdname == 'signtransaction' and config.get('privkey'):
cmd.requires_wallet = False
cmd.requires_password = False
if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):
cmd.requires_password = False
if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):
cmd.requires_network = True
if cmdname in ['createrawtx'] and config.get('unsigned'):
cmd.requires_password = False
cmd.requires_wallet = False
# instanciate wallet for command-line
storage = WalletStorage(config.get_wallet_path()) storage = WalletStorage(config.get_wallet_path())
if storage.file_exists:
sys.exit("Error: Remove the existing wallet first!")
if cmd.name in ['create', 'restore']: def password_dialog():
if storage.file_exists: return prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
sys.exit("Error: Remove the existing wallet first!")
def password_dialog(): if cmdname == 'restore':
return prompt_password("Password (hit return if you do not wish to encrypt your wallet):") text = config.get('text')
password = password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None
if cmd.name == 'restore': try:
text = config.get('text') wallet = Wallet.from_text(text, password, storage)
password = password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None except BaseException as e:
try: sys.exit(str(e))
wallet = Wallet.from_text(text, password, storage) if not config.get('offline'):
except BaseException as e: network = Network(config)
sys.exit(str(e)) network.start()
if not config.get('offline'): wallet.start_threads(network)
network = Network(config) print_msg("Recovering wallet...")
network.start()
wallet.start_threads(network)
print_msg("Recovering wallet...")
wallet.synchronize()
wallet.wait_until_synchronized()
msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
else:
msg = "This wallet was restored offline. It may contain more addresses than displayed."
print_msg(msg)
else:
password = password_dialog()
wallet = Wallet(storage)
seed = wallet.make_seed()
wallet.add_seed(seed, password)
wallet.create_master_keys(password)
wallet.create_main_account(password)
wallet.synchronize() wallet.synchronize()
print_msg("Your wallet generation seed is:\n\"%s\"" % seed) wallet.wait_until_synchronized()
print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.") msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
wallet.storage.write()
print_msg("Wallet saved in '%s'" % wallet.storage.path)
sys.exit(0)
if cmd.requires_wallet and not storage.file_exists:
print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0)
# create wallet instance
wallet = Wallet(storage) if cmd.requires_wallet else None
# notify plugins
always_hook('cmdline_load_wallet', wallet)
# important warning
if cmd.name in ['getprivatekeys']:
print_stderr("WARNING: ALL your private keys are secret.")
print_stderr("Exposing a single private key can compromise your entire wallet!")
print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
# commands needing password
if cmd.requires_password and wallet.use_encryption:
if config.get('password'):
password = config.get('password')
else: else:
password = prompt_password('Password:', False) msg = "This wallet was restored offline. It may contain more addresses than displayed."
if not password: print_msg(msg)
print_msg("Error: Password required")
sys.exit(1)
# check password
try:
seed = wallet.check_password(password)
except InvalidPassword:
print_msg("Error: This password does not decode this wallet.")
sys.exit(1)
else:
password = None
# run the command elif cmdname == 'create':
if cmd.name == 'deseed': password = password_dialog()
wallet = Wallet(storage)
seed = wallet.make_seed()
wallet.add_seed(seed, password)
wallet.create_master_keys(password)
wallet.create_main_account(password)
wallet.synchronize()
print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
elif cmdname == 'deseed':
if not wallet.seed: if not wallet.seed:
print_msg("Error: This wallet has no seed") print_msg("Error: This wallet has no seed")
else: else:
@ -228,26 +168,95 @@ def init_cmdline(config):
wallet.storage.write() wallet.storage.write()
sys.exit(0) sys.exit(0)
elif cmd.name == 'password': wallet.storage.write()
new_password = prompt_password('New password:') print_msg("Wallet saved in '%s'" % wallet.storage.path)
wallet.update_password(password, new_password) sys.exit(0)
wallet.storage.write()
def init_cmdline(config_options):
config = SimpleConfig(config_options)
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
if cmdname == 'signtransaction' and config.get('privkey'):
cmd.requires_wallet = False
cmd.requires_password = False
if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):
cmd.requires_password = False
if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):
cmd.requires_network = True
if cmdname in ['createrawtx'] and config.get('unsigned'):
cmd.requires_password = False
cmd.requires_wallet = False
# instanciate wallet for command-line
storage = WalletStorage(config.get_wallet_path())
if cmd.requires_wallet and not storage.file_exists:
print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0) sys.exit(0)
return cmd, password, wallet # important warning
if cmd.name in ['getprivatekeys']:
print_stderr("WARNING: ALL your private keys are secret.")
print_stderr("Exposing a single private key can compromise your entire wallet!")
print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
# commands needing password
if cmd.requires_password and storage.get('use_encryption'):
if config.get('password'):
password = config.get('password')
else:
password = prompt_password('Password:', False)
if not password:
print_msg("Error: Password required")
sys.exit(1)
else:
password = None
config_options['password'] = password
if cmd.name == 'password':
new_password = prompt_password('New password:')
config_options['new_password'] = new_password
return cmd, password
def run_offline_command(config, cmd, wallet, password): def run_offline_command(config, config_options):
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
storage = WalletStorage(config.get_wallet_path())
wallet = Wallet(storage) if cmd.requires_wallet else None
# check password
if cmd.requires_password and storage.get('use_encryption'):
password = config_options.get('password')
try:
seed = wallet.check_password(password)
except InvalidPassword:
print_msg("Error: This password does not decode this wallet.")
sys.exit(1)
if cmd.requires_network:
print_stderr("Warning: running command offline")
# notify plugins
always_hook('cmdline_load_wallet', wallet)
# arguments passed to function # arguments passed to function
args = map(lambda x: config.get(x), cmd.params) args = map(lambda x: config.get(x), cmd.params)
# decode json arguments # decode json arguments
args = map(json_decode, args) args = map(json_decode, args)
# options # options
args += map(lambda x: config.get(x), cmd.options) args += map(lambda x: config.get(x), cmd.options)
cmd_runner = Commands(config, wallet, None) cmd_runner = Commands(config, wallet, None,
cmd_runner.password = password password=config_options.get('password'),
new_password=config_options.get('new_password'))
func = getattr(cmd_runner, cmd.name) func = getattr(cmd_runner, cmd.name)
result = func(*args) result = func(*args)
# save wallet
wallet.storage.write()
return result return result
@ -317,19 +326,22 @@ if __name__ == '__main__':
gui_name = config.get('gui', 'qt') if cmd_name == 'gui' else 'cmdline' gui_name = config.get('gui', 'qt') if cmd_name == 'gui' else 'cmdline'
plugins = Plugins(config, is_bundle or is_local or is_android, gui_name) plugins = Plugins(config, is_bundle or is_local or is_android, gui_name)
# run command offline # run non-RPC commands separately
if cmd_name not in ['gui', 'daemon']: if cmd_name in ['create', 'restore', 'deseed']:
cmd, password, wallet = init_cmdline(config) run_non_RPC(config)
if not (cmd.requires_network or cmd.requires_wallet) or config.get('offline'): sys.exit(0)
result = run_offline_command(config, cmd, wallet, password)
print_msg(json_encode(result))
wallet.storage.write()
sys.exit(0)
else:
config_options['password'] = password
# check if a daemon is running
server = get_daemon(config) server = get_daemon(config)
# if no daemon is running, run the command offline
if cmd_name not in ['gui', 'daemon']:
init_cmdline(config_options)
if server is None: #not (cmd.requires_network or cmd.requires_wallet) or config.get('offline'):
result = run_offline_command(config, config_options)
print_msg(json_encode(result))
sys.exit(0)
# daemon is running # daemon is running
if server is not None: if server is not None:
cmdname = config_options.get('cmd') cmdname = config_options.get('cmd')

View File

@ -74,21 +74,22 @@ def command(s):
class Commands: class Commands:
def __init__(self, config, wallet, network, callback = None): def __init__(self, config, wallet, network, callback = None, password=None, new_password=None):
self.config = config self.config = config
self.wallet = wallet self.wallet = wallet
self.network = network self.network = network
self._callback = callback self._callback = callback
self.password = None self._password = password
self.new_password = new_password
self.contacts = contacts.Contacts(self.config) self.contacts = contacts.Contacts(self.config)
def _run(self, method, args, password_getter): def _run(self, method, args, password_getter):
cmd = known_commands[method] cmd = known_commands[method]
if cmd.requires_password and self.wallet.use_encryption: if cmd.requires_password and self.wallet.use_encryption:
self.password = apply(password_getter,()) self._password = apply(password_getter,())
f = getattr(self, method) f = getattr(self, method)
result = f(*args) result = f(*args)
self.password = None self._password = None
if self._callback: if self._callback:
apply(self._callback, ()) apply(self._callback, ())
return result return result
@ -120,7 +121,9 @@ class Commands:
@command('wp') @command('wp')
def password(self): def password(self):
"""Change wallet password. """ """Change wallet password. """
raise BaseException('Not a JSON-RPC command') self.wallet.update_password(self._password, self.new_password)
self.wallet.storage.write()
return {'password':self.wallet.use_encryption}
@command('') @command('')
def getconfig(self, key): def getconfig(self, key):
@ -200,7 +203,7 @@ class Commands:
outputs = map(lambda x: ('address', x[0], int(COIN*x[1])), outputs.items()) outputs = map(lambda x: ('address', x[0], int(COIN*x[1])), outputs.items())
tx = Transaction.from_io(tx_inputs, outputs) tx = Transaction.from_io(tx_inputs, outputs)
if not unsigned: if not unsigned:
self.wallet.sign_transaction(tx, self.password) self.wallet.sign_transaction(tx, self._password)
return tx.as_dict() return tx.as_dict()
@command('wp') @command('wp')
@ -212,7 +215,7 @@ class Commands:
pubkey = bitcoin.public_key_from_private_key(privkey) pubkey = bitcoin.public_key_from_private_key(privkey)
t.sign({pubkey:privkey}) t.sign({pubkey:privkey})
else: else:
self.wallet.sign_transaction(t, self.password) self.wallet.sign_transaction(t, self._password)
return t.as_dict() return t.as_dict()
@command('') @command('')
@ -250,7 +253,7 @@ class Commands:
"""Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses.""" """Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
is_list = type(address) is list is_list = type(address) is list
domain = address if is_list else [address] domain = address if is_list else [address]
out = [self.wallet.get_private_key(address, self.password) for address in domain] out = [self.wallet.get_private_key(address, self._password) for address in domain]
return out if is_list else out[0] return out if is_list else out[0]
@command('w') @command('w')
@ -334,19 +337,19 @@ class Commands:
@command('wp') @command('wp')
def getmasterprivate(self): def getmasterprivate(self):
"""Get master private key. Return your wallet\'s master private key""" """Get master private key. Return your wallet\'s master private key"""
return str(self.wallet.get_master_private_key(self.wallet.root_name, self.password)) return str(self.wallet.get_master_private_key(self.wallet.root_name, self._password))
@command('wp') @command('wp')
def getseed(self): def getseed(self):
"""Get seed phrase. Print the generation seed of your wallet.""" """Get seed phrase. Print the generation seed of your wallet."""
s = self.wallet.get_mnemonic(self.password) s = self.wallet.get_mnemonic(self._password)
return s.encode('utf8') return s.encode('utf8')
@command('wp') @command('wp')
def importprivkey(self, privkey): def importprivkey(self, privkey):
"""Import a private key. """ """Import a private key. """
try: try:
addr = self.wallet.import_key(privkey, self.password) addr = self.wallet.import_key(privkey, self._password)
out = "Keypair imported: " + addr out = "Keypair imported: " + addr
except Exception as e: except Exception as e:
out = "Error: " + str(e) out = "Error: " + str(e)
@ -377,7 +380,7 @@ class Commands:
def signmessage(self, address, message): def signmessage(self, address, message):
"""Sign a message with a key. Use quotes if your message contains """Sign a message with a key. Use quotes if your message contains
whitespaces""" whitespaces"""
sig = self.wallet.sign_message(address, message, self.password) sig = self.wallet.sign_message(address, message, self._password)
return base64.b64encode(sig) return base64.b64encode(sig)
@command('') @command('')
@ -415,7 +418,7 @@ class Commands:
tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr) tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
str(tx) #this serializes str(tx) #this serializes
if not unsigned: if not unsigned:
self.wallet.sign_transaction(tx, self.password) self.wallet.sign_transaction(tx, self._password)
return tx return tx
@command('wpn') @command('wpn')
@ -522,7 +525,7 @@ class Commands:
@command('wp') @command('wp')
def decrypt(self, pubkey, encrypted): def decrypt(self, pubkey, encrypted):
"""Decrypt a message encrypted with a public key.""" """Decrypt a message encrypted with a public key."""
return self.wallet.decrypt_message(pubkey, encrypted, self.password) return self.wallet.decrypt_message(pubkey, encrypted, self._password)
def _format_request(self, out): def _format_request(self, out):
pr_str = { pr_str = {
@ -587,7 +590,7 @@ class Commands:
if not alias: if not alias:
raise BaseException('No alias in your configuration') raise BaseException('No alias in your configuration')
alias_addr = self.contacts.resolve(alias)['address'] alias_addr = self.contacts.resolve(alias)['address']
self.wallet.sign_payment_request(address, alias, alias_addr, self.password) self.wallet.sign_payment_request(address, alias, alias_addr, self._password)
@command('w') @command('w')
def rmrequest(self, address): def rmrequest(self, address):

View File

@ -135,7 +135,6 @@ class Daemon(DaemonThread):
return wallet return wallet
def run_cmdline(self, config_options): def run_cmdline(self, config_options):
password = config_options.get('password')
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
cmdname = config.get('cmd') cmdname = config.get('cmd')
cmd = known_commands[cmdname] cmd = known_commands[cmdname]
@ -146,8 +145,9 @@ class Daemon(DaemonThread):
args = map(json_decode, args) args = map(json_decode, args)
# options # options
args += map(lambda x: config.get(x), cmd.options) args += map(lambda x: config.get(x), cmd.options)
cmd_runner = Commands(config, wallet, self.network) cmd_runner = Commands(config, wallet, self.network,
cmd_runner.password = password password=config_options.get('password'),
new_password=config_options.get('new_password'))
func = getattr(cmd_runner, cmd.name) func = getattr(cmd_runner, cmd.name)
result = func(*args) result = func(*args)
return result return result