From acbe67fd1f4d3bd0755707ee90b97295b5d7ff99 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 23 Dec 2015 10:54:31 +0100 Subject: [PATCH] separation between RPC and non-RPC commands. --- electrum | 234 +++++++++++++++++++++++++----------------------- lib/commands.py | 33 +++---- lib/daemon.py | 6 +- 3 files changed, 144 insertions(+), 129 deletions(-) diff --git a/electrum b/electrum index 0056ccd8..e322331f 100755 --- a/electrum +++ b/electrum @@ -107,107 +107,47 @@ def init_gui(config, network, plugins): return gui - -def init_cmdline(config): - +def run_non_RPC(config): 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 storage.file_exists: + sys.exit("Error: Remove the existing wallet first!") - if cmd.name in ['create', 'restore']: - if storage.file_exists: - sys.exit("Error: Remove the existing wallet first!") + def password_dialog(): + return prompt_password("Password (hit return if you do not wish to encrypt your wallet):") - def password_dialog(): - return prompt_password("Password (hit return if you do not wish to encrypt your wallet):") - - if cmd.name == 'restore': - 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 - try: - wallet = Wallet.from_text(text, password, storage) - except BaseException as e: - sys.exit(str(e)) - if not config.get('offline'): - network = Network(config) - 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) + if cmdname == 'restore': + 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 + try: + wallet = Wallet.from_text(text, password, storage) + except BaseException as e: + sys.exit(str(e)) + if not config.get('offline'): + network = Network(config) + network.start() + wallet.start_threads(network) + print_msg("Recovering wallet...") 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.") - - 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') + wallet.wait_until_synchronized() + msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet" else: - password = prompt_password('Password:', False) - if not password: - 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 + msg = "This wallet was restored offline. It may contain more addresses than displayed." + print_msg(msg) - # run the command - if cmd.name == 'deseed': + elif cmdname == 'create': + 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: print_msg("Error: This wallet has no seed") else: @@ -228,26 +168,95 @@ def init_cmdline(config): wallet.storage.write() sys.exit(0) - elif cmd.name == 'password': - new_password = prompt_password('New password:') - wallet.update_password(password, new_password) - wallet.storage.write() + wallet.storage.write() + print_msg("Wallet saved in '%s'" % wallet.storage.path) + sys.exit(0) + + +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) - 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 args = map(lambda x: config.get(x), cmd.params) # decode json arguments args = map(json_decode, args) # options args += map(lambda x: config.get(x), cmd.options) - cmd_runner = Commands(config, wallet, None) - cmd_runner.password = password + cmd_runner = Commands(config, wallet, None, + password=config_options.get('password'), + new_password=config_options.get('new_password')) func = getattr(cmd_runner, cmd.name) result = func(*args) + # save wallet + wallet.storage.write() return result @@ -317,19 +326,22 @@ if __name__ == '__main__': 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) - # run command offline - if cmd_name not in ['gui', 'daemon']: - cmd, password, wallet = init_cmdline(config) - if not (cmd.requires_network or cmd.requires_wallet) or config.get('offline'): - result = run_offline_command(config, cmd, wallet, password) - print_msg(json_encode(result)) - wallet.storage.write() - sys.exit(0) - else: - config_options['password'] = password + # run non-RPC commands separately + if cmd_name in ['create', 'restore', 'deseed']: + run_non_RPC(config) + sys.exit(0) + # check if a daemon is running 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 if server is not None: cmdname = config_options.get('cmd') diff --git a/lib/commands.py b/lib/commands.py index ed351bae..75951973 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -74,21 +74,22 @@ def command(s): 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.wallet = wallet self.network = network self._callback = callback - self.password = None + self._password = password + self.new_password = new_password self.contacts = contacts.Contacts(self.config) def _run(self, method, args, password_getter): cmd = known_commands[method] if cmd.requires_password and self.wallet.use_encryption: - self.password = apply(password_getter,()) + self._password = apply(password_getter,()) f = getattr(self, method) result = f(*args) - self.password = None + self._password = None if self._callback: apply(self._callback, ()) return result @@ -120,7 +121,9 @@ class Commands: @command('wp') def password(self): """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('') def getconfig(self, key): @@ -200,7 +203,7 @@ class Commands: outputs = map(lambda x: ('address', x[0], int(COIN*x[1])), outputs.items()) tx = Transaction.from_io(tx_inputs, outputs) if not unsigned: - self.wallet.sign_transaction(tx, self.password) + self.wallet.sign_transaction(tx, self._password) return tx.as_dict() @command('wp') @@ -212,7 +215,7 @@ class Commands: pubkey = bitcoin.public_key_from_private_key(privkey) t.sign({pubkey:privkey}) else: - self.wallet.sign_transaction(t, self.password) + self.wallet.sign_transaction(t, self._password) return t.as_dict() @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.""" is_list = type(address) is list 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] @command('w') @@ -334,19 +337,19 @@ class Commands: @command('wp') def getmasterprivate(self): """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') def getseed(self): """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') @command('wp') def importprivkey(self, privkey): """Import a private key. """ try: - addr = self.wallet.import_key(privkey, self.password) + addr = self.wallet.import_key(privkey, self._password) out = "Keypair imported: " + addr except Exception as e: out = "Error: " + str(e) @@ -377,7 +380,7 @@ class Commands: def signmessage(self, address, message): """Sign a message with a key. Use quotes if your message contains whitespaces""" - sig = self.wallet.sign_message(address, message, self.password) + sig = self.wallet.sign_message(address, message, self._password) return base64.b64encode(sig) @command('') @@ -415,7 +418,7 @@ class Commands: tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr) str(tx) #this serializes if not unsigned: - self.wallet.sign_transaction(tx, self.password) + self.wallet.sign_transaction(tx, self._password) return tx @command('wpn') @@ -522,7 +525,7 @@ class Commands: @command('wp') def decrypt(self, pubkey, encrypted): """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): pr_str = { @@ -587,7 +590,7 @@ class Commands: if not alias: raise BaseException('No alias in your configuration') 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') def rmrequest(self, address): diff --git a/lib/daemon.py b/lib/daemon.py index 94a97add..de9354dc 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -135,7 +135,6 @@ class Daemon(DaemonThread): return wallet def run_cmdline(self, config_options): - password = config_options.get('password') config = SimpleConfig(config_options) cmdname = config.get('cmd') cmd = known_commands[cmdname] @@ -146,8 +145,9 @@ class Daemon(DaemonThread): args = map(json_decode, args) # options args += map(lambda x: config.get(x), cmd.options) - cmd_runner = Commands(config, wallet, self.network) - cmd_runner.password = password + cmd_runner = Commands(config, wallet, self.network, + password=config_options.get('password'), + new_password=config_options.get('new_password')) func = getattr(cmd_runner, cmd.name) result = func(*args) return result