From ddd6a58cc7427c8f3474d08c348cbd68626152c9 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 31 May 2015 22:42:34 +0200 Subject: [PATCH] commands: extract params and options from arguments --- electrum | 15 -- lib/commands.py | 600 +++++++++++++++++++++++++----------------------- 2 files changed, 310 insertions(+), 305 deletions(-) diff --git a/electrum b/electrum index d26cff304..8362dc7d6 100755 --- a/electrum +++ b/electrum @@ -21,7 +21,6 @@ from decimal import Decimal import json import os import re -import ast import sys import time import traceback @@ -322,20 +321,6 @@ def run_cmdline(config): else: print_msg("Action canceled.") - elif cmd.name == 'getconfig': - key = args[0] - out = config.get(key) - print_msg(out) - - elif cmd.name == 'setconfig': - key, value = args[0:2] - try: - value = ast.literal_eval(value) - except: - pass - config.set_key(key, value, True) - print_msg(True) - elif cmd.name == 'password': new_password = prompt_password('New password:') wallet.update_password(password, new_password) diff --git a/lib/commands.py b/lib/commands.py index bb0aaae4e..64096cd77 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -22,6 +22,7 @@ import time import copy import argparse import json +import ast import util from util import print_msg, format_satoshis, print_stderr @@ -31,226 +32,6 @@ import bitcoin from transaction import Transaction -class Command: - def __init__(self, name, requires_network, requires_wallet, requires_password, params, options, help, description): - self.name = name - self.requires_network = bool(requires_network) - self.requires_wallet = bool(requires_wallet) - self.requires_password = bool(requires_password) - self.params = params - self.options = options - self.help = help - self.description = description - -known_commands = {} - -def register_command(*args): - global known_commands - name = args[0] - known_commands[name] = Command(*args) - - -# command -# requires_network -# requires_wallet -# requires_password -# arguments -# options -register_command('listcontacts', 0, 0, 0, [], [], 'Show your list of contacts', '') -register_command('create', 0, 1, 0, [], [], 'Create a new wallet', '') -register_command('createmultisig', 0, 1, 0, ['num', 'pubkeys'], ['unsigned'], 'Create multisig address', '') -register_command('createrawtx', 0, 1, 1, ['inputs', 'outputs'], ['unsigned'], 'Create a transaction from json inputs', 'The syntax is similar to bitcoind.') -register_command('deseed', 0, 1, 0, [], [], 'Remove seed from wallet.', 'This creates a seedless, watching-only wallet.') -register_command('decodetx', 0, 0, 0, ['tx'], [], 'Decode serialized transaction', '') -register_command('getprivatekeys', 0, 1, 1, ['address'], [], 'Get the private keys of an address', 'Address must be in wallet.') -register_command('dumpprivkeys', 0, 1, 1, [], [], 'Dump private keys from your wallet', '') -register_command('freeze', 0, 1, 0, ['address'], [], 'Freeze address', 'Freeze the funds at one of your wallet\'s addresses') -register_command('getalias', 0, 0, 0, ['key'], ['nocheck'], 'Retrieve alias', 'Lookup in your list of contacts, and for an OpenAlias DNS record') -register_command('getbalance', 1, 1, 0, [], [], 'Return the balance of your wallet', '') -register_command('getservers', 1, 0, 0, [], [], 'Return the list of available servers', '') -register_command('getaddressbalance', 1, 0, 0, ['address'], [], 'Return the balance of an address', '') -register_command('getaddresshistory', 1, 0, 0, ['address'], [], 'Return the transaction history of a wallet address', '') -register_command('getconfig', 0, 0, 0, ['key'], [], 'Return a configuration variable', '') -register_command('getpubkeys', 0, 1, 0, ['address'], [], 'Return the public keys for a wallet address', '') -register_command('gettransaction', 1, 0, 0, ['txid'], ['deserialized'], 'Retrieve a transaction', '') -register_command('getseed', 0, 1, 1, [], [], 'Get seed phrase', 'Print the generation seed of your wallet.') -register_command('getmpk', 0, 1, 0, [], [], 'Get Master Public Key', 'Return your wallet\'s master public key') -register_command('help', 0, 0, 0, [], [], 'Print help on a command', '') -register_command('history', 1, 1, 0, [], [], 'Wallet history', 'Returns the transaction history of your wallet') -register_command('importprivkey', 0, 1, 1, ['privkey'], [], 'Import a private key', '') -register_command('ismine', 0, 1, 0, ['address'], [], 'Check if address is in wallet', 'Return true if and only address is in wallet') -register_command('listaddresses', 0, 1, 0, [], ['show_all', 'show_labels', 'frozen', 'unused', 'funded', 'show_balance'], - 'List wallet addresses', 'Returns your list of addresses.') -register_command('listunspent', 1, 1, 0, [], [], 'List unspent outputs', 'Returns the list of unspent transaction outputs in your wallet.') -register_command('getaddressunspent', 1, 0, 0, ['address'], [], 'Returns the list of unspent inputs for an address', '') -register_command('mktx', 0, 1, 1, ['destination', 'amount'], ['tx_fee', 'from_addr', 'change_addr', 'nocheck', 'unsigned', 'deserialized'], - 'Create a transaction', '') -register_command('payto', 1, 1, 1, ['destination', 'amount'], ['tx_fee', 'from_addr', 'change_addr', 'nocheck'], 'Create and broadcast a transaction.', '') -register_command('mktx_csv', 0, 1, 1, ['csv_file'], ['tx_fee', 'from_addr', 'change_addr', 'nocheck', 'unsigned', 'deserialized'], - 'Create a multi-output transaction', '') -register_command('payto_csv', 1, 1, 1, ['csv_file'], ['tx_fee', 'from_addr', 'change_addr', 'nocheck'], 'Create and broadcast multi-output transaction.', '') -register_command('password', 0, 1, 1, [], [], 'Change your password', '') -register_command('restore', 1, 1, 0, [], ['gap_limit', 'mpk', 'concealed'], 'Restore a wallet from seed', '') -register_command('searchcontacts', 0, 1, 0, ['query'], [], 'Search through contacts, return matching entries', '') -register_command('setconfig', 0, 0, 0, ['key', 'value'], [], 'Set a configuration variable', '') -register_command('setlabel', 0, 1, 0, ['item', 'label'], [], 'Assign a label to an item', 'Item may be a bitcoin address or a transaction ID') -register_command('sendtx', 1, 0, 0, ['tx'], [], 'Broadcast a transaction to the network', '') -register_command('signtransaction', 0, 1, 1, ['tx'], ['privkey'], - 'Sign a transaction', 'The wallet keys will be used unless a private key is provided.') -register_command('signmessage', 0, 1, 1, ['address', 'message'], [], - 'Sign a message with a key', 'Use quotes if your message contains whitespaces') -register_command('unfreeze', 0, 1, 0, ['address'], [], 'Unfreeze address', 'Unfreeze the funds at one of your wallet\'s address') -register_command('validateaddress', 0, 0, 0, ['address'], [], 'Check that the address is valid', '') -register_command('verifymessage', 0, 0, 0, ['address', 'signature', 'message'], [], 'Verify a signature', '') -register_command('version', 0, 0, 0, [], [], 'Return the version of your client', '') -register_command('encrypt', 0, 0, 0, ['pubkey', 'message'], [], - 'Encrypt a message with a public key', 'Use quotes if the message contains whitespaces.') -register_command('decrypt', 0, 1, 1, ['pubkey', 'encrypted'], [], 'Decrypt a message encrypted with a public key', '') -register_command('getmerkle', 1, 0, 0, ['txid', 'height'], [], 'Get Merkle branch of a transaction included in a block', '') -register_command('getproof', 1, 0, 0, ['address'], [], 'Get Merkle branch of an address in the UTXO set', '') -register_command('getutxoaddress', 1, 0, 0, ['txid', 'pos'], [], 'Get the address of an unspent transaction output', '') -register_command('sweep', 1, 0, 0, ['privkey', 'destination'], ['tx_fee'], - 'Sweep private key', 'Returns a transaction that spends UTXOs from privkey to a destination address. The transaction is not broadcasted.') -register_command('make_seed', 0, 0, 0, [], ['nbits', 'entropy', 'language'], 'Create a seed', '') -register_command('check_seed', 0, 0, 0, ['seed'], ['entropy', 'language'], 'Check that a seed was generated with given entropy', '') - -param_descriptions = { - 'privkey': 'Private key. Type \'?\' to get a prompt.', - 'destination': 'Bitcoin address, contact or alias', - 'address': 'Bitcoin address', - 'seed': 'Seed phrase', - 'txid': 'Transaction ID', - 'pos': 'Position', - 'heigh': 'Block height', - 'tx': 'Serialized transaction (hexadecimal)', - 'key': 'Variable name', - 'pubkey': 'Public key', - 'message': 'Clear text message. Use quotes if it contains spaces.', - 'encrypted': 'Encrypted message', - 'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.', - 'csv_file': 'CSV file of recipient, amount', -} - -command_options = { - 'password': ("-W", "--password", None, "Password"), - 'concealed': ("-C", "--concealed", False, "Don't echo seed to console when restoring"), - 'show_all': ("-a", "--all", False, "Include change addresses"), - 'frozen': (None, "--frozen", False, "Show only frozen addresses"), - 'unused': (None, "--unused", False, "Show only unused addresses"), - 'funded': (None, "--funded", False, "Show only funded addresses"), - 'show_balance':("-b", "--balance", False, "Show the balances of listed addresses"), - 'show_labels': ("-l", "--labels", False, "Show the labels of listed addresses"), - 'nocheck': (None, "--nocheck", False, "Do not verify aliases"), - 'tx_fee': ("-f", "--fee", None, "Transaction fee (in BTC)"), - 'from_addr': ("-F", "--from", None, "Source address. If it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet."), - 'change_addr': ("-c", "--change", None, "Change address. Default is a spare address, or the source address if it's not in the wallet"), - 'nbits': (None, "--nbits", 128, "Number of bits of entropy"), - 'entropy': (None, "--entropy", 1, "Custom entropy"), - 'language': ("-L", "--lang", None, "Default language for wordlist"), - 'gap_limit': ("-G", "--gap", None, "Gap limit"), - 'mpk': (None, "--mpk", None, "Restore from master public key"), - 'deserialized':("-d", "--deserialized",False, "Return deserialized transaction"), - 'privkey': (None, "--privkey", None, "Private key. Set to '?' to get a prompt."), - 'unsigned': ("-u", "--unsigned", False, "Do not sign transaction"), -} - - -arg_types = { - 'num':int, - 'nbits':int, - 'entropy':long, - 'pubkeys': json.loads, - 'inputs': json.loads, - 'outputs': json.loads, - 'tx_fee': lambda x: Decimal(x) if x is not None else None, - 'amount': lambda x: Decimal(x) if x!='!' else '!', -} - - -def set_default_subparser(self, name, args=None): - """see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand""" - subparser_found = False - for arg in sys.argv[1:]: - if arg in ['-h', '--help']: # global help if no subparser - break - else: - for x in self._subparsers._actions: - if not isinstance(x, argparse._SubParsersAction): - continue - for sp_name in x._name_parser_map.keys(): - if sp_name in sys.argv[1:]: - subparser_found = True - if not subparser_found: - # insert default in first position, this implies no - # global options without a sub_parsers specified - if args is None: - sys.argv.insert(1, name) - else: - args.insert(0, name) - -argparse.ArgumentParser.set_default_subparser = set_default_subparser - -def add_network_options(parser): - parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only") - parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)") - parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http") - -def get_parser(run_gui, run_daemon, run_cmdline): - # parent parser, because set_default_subparser removes global options - parent_parser = argparse.ArgumentParser('parent', add_help=False) - parent_parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Show debugging information") - parent_parser.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") - # create main parser - parser = argparse.ArgumentParser( - parents=[parent_parser], - epilog="Run 'electrum help ' to see the help for a command") - subparsers = parser.add_subparsers(dest='cmd', metavar='') - # gui - parser_gui = subparsers.add_parser('gui', parents=[parent_parser], description="Run Electrum's Graphical User Interface.", help="Run GUI (default)") - parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)") - parser_gui.set_defaults(func=run_gui) - parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'lite', 'gtk', 'text', 'stdio']) - parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup") - parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI") - parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run the GUI offline") - parser_gui.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") - add_network_options(parser_gui) - # daemon - parser_daemon = subparsers.add_parser('daemon', parents=[parent_parser], help="Run Daemon") - parser_daemon.add_argument("subcommand", choices=['start', 'status', 'stop']) - parser_daemon.set_defaults(func=run_daemon) - add_network_options(parser_daemon) - # commands - for cmdname in sorted(known_commands.keys()): - cmd = known_commands[cmdname] - p = subparsers.add_parser(cmdname, parents=[parent_parser], help=cmd.help, description=cmd.help + '. ' + cmd.description) - p.set_defaults(func=run_cmdline) - if cmd.requires_password: - p.add_argument("-W", "--password", dest="password", default=None, help="password") - if cmd.requires_network: - p.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run command offline") - if cmd.requires_wallet: - p.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") - for optname in cmd.options: - a, b, default, help = command_options[optname] - action = "store_true" if type(default) is bool else 'store' - args = (a, b) if a else (b,) - if action == 'store': - _type = arg_types.get(optname, str) - p.add_argument(*args, dest=optname, action=action, default=default, help=help, type=_type) - else: - p.add_argument(*args, dest=optname, action=action, default=default, help=help) - - for param in cmd.params: - h = param_descriptions.get(param, '') - _type = arg_types.get(param, str) - p.add_argument(param, help=h, type=_type) - # 'gui' is the default command - parser.set_default_subparser('gui') - return parser - - - class Commands: def __init__(self, config, wallet, network, callback = None): @@ -275,28 +56,45 @@ class Commands: def help(self): return 'Commands: ' + ', '.join(sorted(known_commands.keys())) - def make_seed(self, nbits, custom_entropy, language): + # dummy functions for register + def create(self): pass + def restore(self): pass + def deseed(self): pass + def password(self): pass + + def getconfig(self, key): + return self.config.get(key) + + def setconfig(self, key, value): + try: + value = ast.literal_eval(value) + except: + pass + self.config.set_key(key, value, True) + return True + + def make_seed(self, nbits=128, entropy=1, language=None): from mnemonic import Mnemonic s = Mnemonic(language).make_seed(nbits, custom_entropy=custom_entropy) return s.encode('utf8') - def check_seed(self, seed, custom_entropy, language): + def check_seed(self, seed, entropy=1, language=None): from mnemonic import Mnemonic - return Mnemonic(language).check_seed(seed, custom_entropy) + return Mnemonic(language).check_seed(seed, entropy) - def getaddresshistory(self, addr): - return self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0] + def getaddresshistory(self, address): + return self.network.synchronous_get([('blockchain.address.get_history', [address])])[0] def listunspent(self): l = copy.deepcopy(self.wallet.get_spendable_coins(exclude_frozen = False)) for i in l: i["value"] = str(Decimal(i["value"])/100000000) return l - def getaddressunspent(self, addr): - return self.network.synchronous_get([('blockchain.address.listunspent',[addr])])[0] + def getaddressunspent(self, address): + return self.network.synchronous_get([('blockchain.address.listunspent', [address])])[0] - def getutxoaddress(self, txid, num): - r = self.network.synchronous_get([ ('blockchain.utxo.get_address',[txid, num]) ]) + def getutxoaddress(self, txid, pos): + r = self.network.synchronous_get([('blockchain.utxo.get_address', [txid, pos])]) if r: return {'address':r[0]} @@ -319,8 +117,8 @@ class Commands: self.wallet.sign_transaction(tx, self.password) return tx - def signtransaction(self, raw_tx, privkey=None): - tx = Transaction(raw_tx) + def signtransaction(self, tx, privkey=None): + tx = Transaction(tx) tx.deserialize() if privkey: pubkey = bitcoin.public_key_from_private_key(sec) @@ -329,13 +127,13 @@ class Commands: self.wallet.sign_transaction(tx, self.password) return tx - def decodetx(self, raw): - tx = Transaction(raw) - return tx.deserialize() + def decodetx(self, tx): + t = Transaction(rawtx) + return t.deserialize() - def sendtx(self, raw): - tx = Transaction(raw) - return self.network.synchronous_get([('blockchain.transaction.broadcast', [str(tx)])])[0] + def sendtx(self, tx): + t = Transaction(raw) + return self.network.synchronous_get([('blockchain.transaction.broadcast', [str(t)])])[0] def createmultisig(self, num, pubkeys): assert isinstance(pubkeys, list), (type(num), type(pubkeys)) @@ -343,36 +141,30 @@ class Commands: address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) return {'address':address, 'redeemScript':redeem_script} - def freeze(self, addr): - return self.wallet.set_frozen_state([addr], True) + def freeze(self, address): + return self.wallet.set_frozen_state([address], True) - def unfreeze(self, addr): - return self.wallet.set_frozen_state([addr], False) + def unfreeze(self, address): + return self.wallet.set_frozen_state([address], False) - def getprivatekeys(self, addr): - return self.wallet.get_private_key(addr, self.password) + def getprivatekeys(self, address): + return self.wallet.get_private_key(address, self.password) - def ismine(self, addr): - return self.wallet.is_mine(addr) + def ismine(self, address): + return self.wallet.is_mine(address) - def dumpprivkeys(self, addresses = None): - if addresses is None: - addresses = self.wallet.addresses(True) - return [self.wallet.get_private_key(address, self.password) for address in addresses] + def dumpprivkeys(self, domain=None): + if domain is None: + domain = self.wallet.addresses(True) + return [self.wallet.get_private_key(address, self.password) for address in domain] - def validateaddress(self, addr): - isvalid = is_address(addr) - out = { 'isvalid':isvalid } - if isvalid: - out['address'] = addr - return out + def validateaddress(self, address): + return is_address(address) - def getpubkeys(self, addr): - out = { 'address':addr } - out['pubkeys'] = self.wallet.get_public_keys(addr) - return out + def getpubkeys(self, address): + return self.wallet.get_public_keys(address) - def getbalance(self, account= None): + def getbalance(self, account=None): if account is None: c, u, x = self.wallet.get_balance() else: @@ -384,21 +176,21 @@ class Commands: out["unmatured"] = str(Decimal(x)/100000000) return out - def getaddressbalance(self, addr): - out = self.network.synchronous_get([ ('blockchain.address.get_balance',[addr]) ])[0] + def getaddressbalance(self, address): + out = self.network.synchronous_get([('blockchain.address.get_balance', [address])])[0] out["confirmed"] = str(Decimal(out["confirmed"])/100000000) out["unconfirmed"] = str(Decimal(out["unconfirmed"])/100000000) return out - def getproof(self, addr): - p = self.network.synchronous_get([ ('blockchain.address.get_proof',[addr]) ])[0] + def getproof(self, address): + p = self.network.synchronous_get([('blockchain.address.get_proof', [address])])[0] out = [] for i,s in p: out.append(i) return out def getmerkle(self, txid, height): - return self.network.synchronous_get([ ('blockchain.transaction.get_merkle', [txid, int(height)]) ])[0] + return self.network.synchronous_get([('blockchain.transaction.get_merkle', [txid, int(height)])])[0] def getservers(self): while not self.network.is_up_to_date(): @@ -416,20 +208,20 @@ class Commands: s = self.wallet.get_mnemonic(self.password) return s.encode('utf8') - def importprivkey(self, sec): + def importprivkey(self, privkey): try: - addr = self.wallet.import_key(sec,self.password) + addr = self.wallet.import_key(privkey, self.password) out = "Keypair imported: ", addr except Exception as e: out = "Error: Keypair import failed: " + str(e) return out - def sweep(self, privkey, recipient, fee=None, nocheck=False): + def sweep(self, privkey, destination, tx_fee=None, nocheck=False): resolver = lambda x: self.contacts.resolve(x, nocheck)['address'] - dest = resolver(recipient) - if fee is None: - fee = 0.0001 - fee = int(Decimal(fee)*100000000) + dest = resolver(destination) + if tx_fee is None: + tx_fee = 0.0001 + fee = int(Decimal(tx_fee)*100000000) return Transaction.sweep([privkey], self.network, dest, fee) def signmessage(self, address, message): @@ -481,27 +273,27 @@ class Commands: outputs.append((address, amount)) return outputs - def mktx(self, to_address, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, deserialized=False): + def mktx(self, destination, amount, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, deserialized=False): domain = [from_addr] if from_addr else None - tx = self._mktx([(to_address, amount)], fee, change_addr, domain, nocheck, unsigned, deserialized) + tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned, deserialized) return tx - def mktx_csv(self, path, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, deserialized=False): + def mktx_csv(self, csv_file, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, deserialized=False): domain = [from_addr] if from_addr else None - outputs = self._read_csv(path) - tx = self._mktx(outputs, fee, change_addr, domain, nocheck, unsigned, deserialized) + outputs = self._read_csv(csv_file) + tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned, deserialized) return tx - def payto(self, to_address, amount, fee=None, from_addr=None, change_addr=None, nocheck=False): + def payto(self, destination, amount, tx_fee=None, from_addr=None, change_addr=None, nocheck=False): domain = [from_addr] if from_addr else None - tx = self._mktx([(to_address, amount)], fee, change_addr, domain, nocheck) + tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck) r, h = self.wallet.sendtx(tx) return h - def payto_csv(self, path, fee = None, from_addr=None, change_addr=None, nocheck=False): + def payto_csv(self, csv_file, tx_fee=None, from_addr=None, change_addr=None, nocheck=False): domain = [from_addr] if from_addr else None - outputs = self._read_csv(path) - tx = self._mktx(outputs, fee, change_addr, domain, nocheck) + outputs = self._read_csv(csv_file) + tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck) r, h = self.wallet.sendtx(tx) return h @@ -536,12 +328,12 @@ class Commands: results[key] = value return results - def listaddresses(self, show_change=False, show_label=False, frozen=False, unused=False, funded=False, show_balance=False): + def listaddresses(self, show_all=False, show_labels=False, frozen=False, unused=False, funded=False, show_balance=False): out = [] for addr in self.wallet.addresses(True): if frozen and not self.wallet.is_frozen(addr): continue - if not show_change and self.wallet.is_change(addr): + if not show_all and self.wallet.is_change(addr): continue if unused and self.wallet.is_used(addr): continue @@ -550,15 +342,15 @@ class Commands: item = addr if show_balance: item += ", "+ format_satoshis(sum(self.wallet.get_addr_balance(addr))) - if show_label: + if show_labels: item += ', ' + self.wallet.labels.get(addr,'') out.append(item) return out - def gettransaction(self, tx_hash, deserialized=False): - tx = self.wallet.transactions.get(tx_hash) if self.wallet else None + def gettransaction(self, txid, deserialized=False): + tx = self.wallet.transactions.get(txid) if self.wallet else None if tx is None and self.network: - raw = self.network.synchronous_get([('blockchain.transaction.get', [tx_hash])])[0] + raw = self.network.synchronous_get([('blockchain.transaction.get', [txid])])[0] if raw: tx = Transaction(raw) else: @@ -568,5 +360,233 @@ class Commands: def encrypt(self, pubkey, message): return bitcoin.encrypt_message(message, pubkey) - def decrypt(self, pubkey, message): - return self.wallet.decrypt_message(pubkey, message, self.password) + def decrypt(self, pubkey, encrypted): + return self.wallet.decrypt_message(pubkey, encrypted, self.password) + + + +class Command: + def __init__(self, name, requires_network, requires_wallet, requires_password, help, description): + self.name = name + self.requires_network = bool(requires_network) + self.requires_wallet = bool(requires_wallet) + self.requires_password = bool(requires_password) + self.help = help + self.description = description + # compute params and options + func = getattr(Commands, name) + varnames = func.func_code.co_varnames[1:func.func_code.co_argcount] + defaults = func.func_defaults + if defaults: + n = len(defaults) + self.params, self.options = list(varnames[:-n]), list(varnames[-n:]) + else: + self.params, self.options = list(varnames), [] + + + +known_commands = {} + +def register_command(*args): + global known_commands + name = args[0] + known_commands[name] = Command(*args) + + +# command +# requires_network +# requires_wallet +# requires_password +# arguments +# options +register_command('listcontacts', 0, 0, 0, 'Show your list of contacts', '') +register_command('create', 0, 1, 0, 'Create a new wallet', '') +register_command('createmultisig', 0, 1, 0, 'Create multisig address', '') +register_command('createrawtx', 0, 1, 1, 'Create a transaction from json inputs', 'The syntax is similar to bitcoind.') +register_command('deseed', 0, 1, 0, 'Remove seed from wallet.', 'This creates a seedless, watching-only wallet.') +register_command('decodetx', 0, 0, 0, 'Decode serialized transaction', '') +register_command('getprivatekeys', 0, 1, 1, 'Get the private keys of an address', 'Address must be in wallet.') +register_command('dumpprivkeys', 0, 1, 1, 'Dump private keys from your wallet', '') +register_command('freeze', 0, 1, 0, 'Freeze address', 'Freeze the funds at one of your wallet\'s addresses') +register_command('getalias', 0, 0, 0, 'Retrieve alias', 'Lookup in your list of contacts, and for an OpenAlias DNS record') +register_command('getbalance', 1, 1, 0, 'Return the balance of your wallet', '') +register_command('getservers', 1, 0, 0, 'Return the list of available servers', '') +register_command('getaddressbalance', 1, 0, 0, 'Return the balance of an address', '') +register_command('getaddresshistory', 1, 0, 0, 'Return the transaction history of a wallet address', '') +register_command('getconfig', 0, 0, 0, 'Return a configuration variable', '') +register_command('getpubkeys', 0, 1, 0, 'Return the public keys for a wallet address', '') +register_command('gettransaction', 1, 0, 0, 'Retrieve a transaction', '') +register_command('getseed', 0, 1, 1, 'Get seed phrase', 'Print the generation seed of your wallet.') +register_command('getmpk', 0, 1, 0, 'Get Master Public Key', 'Return your wallet\'s master public key') +register_command('help', 0, 0, 0, 'Print help on a command', '') +register_command('history', 1, 1, 0, 'Wallet history', 'Returns the transaction history of your wallet') +register_command('importprivkey', 0, 1, 1, 'Import a private key', '') +register_command('ismine', 0, 1, 0, 'Check if address is in wallet', 'Return true if and only address is in wallet') +register_command('listaddresses', 0, 1, 0, 'List wallet addresses', 'Returns your list of addresses.') +register_command('listunspent', 1, 1, 0, 'List unspent outputs', 'Returns the list of unspent transaction outputs in your wallet.') +register_command('getaddressunspent', 1, 0, 0, 'Returns the list of unspent inputs for an address', '') +register_command('mktx', 0, 1, 1, 'Create a transaction', '') +register_command('payto', 1, 1, 1, 'Create and broadcast a transaction.', '') +register_command('mktx_csv', 0, 1, 1, 'Create a multi-output transaction', '') +register_command('payto_csv', 1, 1, 1, 'Create and broadcast multi-output transaction.', '') +register_command('password', 0, 1, 1, 'Change your password', '') +register_command('restore', 1, 1, 0, 'Restore a wallet from seed', '') +register_command('searchcontacts', 0, 1, 0, 'Search through contacts, return matching entries', '') +register_command('setconfig', 0, 0, 0, 'Set a configuration variable', '') +register_command('setlabel', 0, 1, 0, 'Assign a label to an item', 'Item may be a bitcoin address or a transaction ID') +register_command('sendtx', 1, 0, 0, 'Broadcast a transaction to the network', '') +register_command('signtransaction', 0, 1, 1, 'Sign a transaction', 'The wallet keys will be used unless a private key is provided.') +register_command('signmessage', 0, 1, 1, 'Sign a message with a key', 'Use quotes if your message contains whitespaces') +register_command('unfreeze', 0, 1, 0, 'Unfreeze address', 'Unfreeze the funds at one of your wallet\'s address') +register_command('validateaddress', 0, 0, 0, 'Check that the address is valid', '') +register_command('verifymessage', 0, 0, 0, 'Verify a signature', '') +register_command('version', 0, 0, 0, 'Return the version of your client', '') +register_command('encrypt', 0, 0, 0, 'Encrypt a message with a public key', 'Use quotes if the message contains whitespaces.') +register_command('decrypt', 0, 1, 1, 'Decrypt a message encrypted with a public key', '') +register_command('getmerkle', 1, 0, 0, 'Get Merkle branch of a transaction included in a block', '') +register_command('getproof', 1, 0, 0, 'Get Merkle branch of an address in the UTXO set', '') +register_command('getutxoaddress', 1, 0, 0, 'Get the address of an unspent transaction output', '') +register_command('sweep', 1, 0, 0, 'Sweep private key', + 'Returns a transaction that spends UTXOs from privkey to a destination address. The transaction is not broadcasted.') +register_command('make_seed', 0, 0, 0, 'Create a seed', '') +register_command('check_seed', 0, 0, 0, 'Check that a seed was generated with given entropy', '') + +param_descriptions = { + 'privkey': 'Private key. Type \'?\' to get a prompt.', + 'destination': 'Bitcoin address, contact or alias', + 'address': 'Bitcoin address', + 'seed': 'Seed phrase', + 'txid': 'Transaction ID', + 'pos': 'Position', + 'heigh': 'Block height', + 'tx': 'Serialized transaction (hexadecimal)', + 'key': 'Variable name', + 'pubkey': 'Public key', + 'message': 'Clear text message. Use quotes if it contains spaces.', + 'encrypted': 'Encrypted message', + 'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.', + 'csv_file': 'CSV file of recipient, amount', +} + +command_options = { + 'password': ("-W", "--password", None, "Password"), + 'concealed': ("-C", "--concealed", False, "Don't echo seed to console when restoring"), + 'show_all': ("-a", "--all", False, "Include change addresses"), + 'frozen': (None, "--frozen", False, "Show only frozen addresses"), + 'unused': (None, "--unused", False, "Show only unused addresses"), + 'funded': (None, "--funded", False, "Show only funded addresses"), + 'show_balance':("-b", "--balance", False, "Show the balances of listed addresses"), + 'show_labels': ("-l", "--labels", False, "Show the labels of listed addresses"), + 'nocheck': (None, "--nocheck", False, "Do not verify aliases"), + 'tx_fee': ("-f", "--fee", None, "Transaction fee (in BTC)"), + 'from_addr': ("-F", "--from", None, "Source address. If it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet."), + 'change_addr': ("-c", "--change", None, "Change address. Default is a spare address, or the source address if it's not in the wallet"), + 'nbits': (None, "--nbits", 128, "Number of bits of entropy"), + 'entropy': (None, "--entropy", 1, "Custom entropy"), + 'language': ("-L", "--lang", None, "Default language for wordlist"), + 'gap_limit': ("-G", "--gap", None, "Gap limit"), + 'mpk': (None, "--mpk", None, "Restore from master public key"), + 'deserialized':("-d", "--deserialized",False, "Return deserialized transaction"), + 'privkey': (None, "--privkey", None, "Private key. Set to '?' to get a prompt."), + 'unsigned': ("-u", "--unsigned", False, "Do not sign transaction"), + 'domain': ("-D", "--domain", None, "List of addresses"), + 'account': (None, "--account", None, "Account"), +} + + +arg_types = { + 'num':int, + 'nbits':int, + 'entropy':long, + 'pubkeys': json.loads, + 'inputs': json.loads, + 'outputs': json.loads, + 'tx_fee': lambda x: Decimal(x) if x is not None else None, + 'amount': lambda x: Decimal(x) if x!='!' else '!', +} + + +def set_default_subparser(self, name, args=None): + """see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand""" + subparser_found = False + for arg in sys.argv[1:]: + if arg in ['-h', '--help']: # global help if no subparser + break + else: + for x in self._subparsers._actions: + if not isinstance(x, argparse._SubParsersAction): + continue + for sp_name in x._name_parser_map.keys(): + if sp_name in sys.argv[1:]: + subparser_found = True + if not subparser_found: + # insert default in first position, this implies no + # global options without a sub_parsers specified + if args is None: + sys.argv.insert(1, name) + else: + args.insert(0, name) + +argparse.ArgumentParser.set_default_subparser = set_default_subparser + +def add_network_options(parser): + parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only") + parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)") + parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http") + +from util import profiler + +@profiler +def get_parser(run_gui, run_daemon, run_cmdline): + # parent parser, because set_default_subparser removes global options + parent_parser = argparse.ArgumentParser('parent', add_help=False) + parent_parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Show debugging information") + parent_parser.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") + # create main parser + parser = argparse.ArgumentParser( + parents=[parent_parser], + epilog="Run 'electrum help ' to see the help for a command") + subparsers = parser.add_subparsers(dest='cmd', metavar='') + # gui + parser_gui = subparsers.add_parser('gui', parents=[parent_parser], description="Run Electrum's Graphical User Interface.", help="Run GUI (default)") + parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)") + parser_gui.set_defaults(func=run_gui) + parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'lite', 'gtk', 'text', 'stdio']) + parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup") + parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI") + parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run the GUI offline") + parser_gui.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") + add_network_options(parser_gui) + # daemon + parser_daemon = subparsers.add_parser('daemon', parents=[parent_parser], help="Run Daemon") + parser_daemon.add_argument("subcommand", choices=['start', 'status', 'stop']) + parser_daemon.set_defaults(func=run_daemon) + add_network_options(parser_daemon) + # commands + for cmdname in sorted(known_commands.keys()): + cmd = known_commands[cmdname] + p = subparsers.add_parser(cmdname, parents=[parent_parser], help=cmd.help, description=cmd.help + '. ' + cmd.description) + p.set_defaults(func=run_cmdline) + if cmd.requires_password: + p.add_argument("-W", "--password", dest="password", default=None, help="password") + if cmd.requires_network: + p.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run command offline") + if cmd.requires_wallet: + p.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") + for optname in cmd.options: + a, b, default, help = command_options[optname] + action = "store_true" if type(default) is bool else 'store' + args = (a, b) if a else (b,) + if action == 'store': + _type = arg_types.get(optname, str) + p.add_argument(*args, dest=optname, action=action, default=default, help=help, type=_type) + else: + p.add_argument(*args, dest=optname, action=action, default=default, help=help) + + for param in cmd.params: + h = param_descriptions.get(param, '') + _type = arg_types.get(param, str) + p.add_argument(param, help=h, type=_type) + # 'gui' is the default command + parser.set_default_subparser('gui') + return parser