tcommands: use decorator to register commands - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 4d9be9a6d2920d660779a30aad1f9f4b4b100e1a DIR parent 7e20901e3bc42e881d52b8647fe504a470163fd8 HTML Author: ThomasV <thomasv@gitorious> Date: Mon, 1 Jun 2015 06:10:06 +0200 commands: use decorator to register commands Diffstat: M lib/commands.py | 175 ++++++++++++++++--------------- 1 file changed, 88 insertions(+), 87 deletions(-) --- DIR diff --git a/lib/commands.py b/lib/commands.py t@@ -23,15 +23,51 @@ import copy import argparse import json import ast +from functools import wraps +from decimal import Decimal import util from util import print_msg, format_satoshis, print_stderr -from bitcoin import is_address, hash_160_to_bc_address, hash_160 -from decimal import Decimal import bitcoin +from bitcoin import is_address, hash_160_to_bc_address, hash_160 from transaction import Transaction +known_commands = {} + +class Command: + + def __init__(self, func, s): + self.name = func.__name__ + self.requires_network = 'n' in s + self.requires_wallet = 'w' in s + self.requires_password = 'p' in s + self.description = func.__doc__ + self.help = self.description.split('.')[0] + varnames = func.func_code.co_varnames[1:func.func_code.co_argcount] + self.defaults = func.func_defaults + if self.defaults: + n = len(self.defaults) + self.params = list(varnames[:-n]) + self.options = list(varnames[-n:]) + else: + self.params = list(varnames) + self.options = [] + self.defaults = [] + + +def command(s): + def decorator(func): + global known_commands + name = func.__name__ + known_commands[name] = Command(func, s) + @wraps(func) + def func_wrapper(*args): + return func(*args) + return func_wrapper + return decorator + + class Commands: def __init__(self, config, wallet, network, callback = None): t@@ -53,27 +89,34 @@ class Commands: apply(self._callback, ()) return result + @command('') def help(self): """Print help""" return 'Commands: ' + ', '.join(sorted(known_commands.keys())) + @command('') def create(self): """Create a new wallet""" + @command('') def restore(self): """Restore a wallet from seed. """ + @command('') def deseed(self): """Remove seed from wallet. This creates a seedless, watching-only wallet.""" + @command('wp') def password(self): """Change wallet password. """ + @command('') def getconfig(self, key): """Return a configuration variable. """ return self.config.get(key) + @command('') def setconfig(self, key, value): """Set a configuration variable. """ try: t@@ -83,37 +126,44 @@ class Commands: self.config.set_key(key, value, True) return True + @command('') def make_seed(self, nbits=128, entropy=1, language=None): """Create a seed""" from mnemonic import Mnemonic s = Mnemonic(language).make_seed(nbits, custom_entropy=custom_entropy) return s.encode('utf8') + @command('') def check_seed(self, seed, entropy=1, language=None): """Check that a seed was generated with given entropy""" from mnemonic import Mnemonic return Mnemonic(language).check_seed(seed, entropy) + @command('n') def getaddresshistory(self, address): """Return the transaction history of a wallet address.""" return self.network.synchronous_get([('blockchain.address.get_history', [address])])[0] + @command('n') def listunspent(self): """List unspent outputs. Returns the list of unspent transaction outputs in your wallet.""" l = copy.deepcopy(self.wallet.get_spendable_coins(exclude_frozen = False)) for i in l: i["value"] = str(Decimal(i["value"])/100000000) return l + @command('n') def getaddressunspent(self, address): """Returns the list of unspent inputs for an address. """ return self.network.synchronous_get([('blockchain.address.listunspent', [address])])[0] + @command('n') def getutxoaddress(self, txid, pos): """Get the address of an unspent transaction output""" r = self.network.synchronous_get([('blockchain.utxo.get_address', [txid, pos])]) if r: return {'address':r[0]} + @command('wp') def createrawtx(self, inputs, outputs, unsigned=False): """Create a transaction from json inputs. The syntax is similar to bitcoind.""" coins = self.wallet.get_spendable_coins(exclude_frozen = False) t@@ -134,6 +184,7 @@ class Commands: self.wallet.sign_transaction(tx, self.password) return tx + @command('wp') def signtransaction(self, tx, privkey=None): """Sign a transaction. The wallet keys will be used unless a private key is provided.""" t = Transaction(tx) t@@ -145,16 +196,19 @@ class Commands: self.wallet.sign_transaction(t, self.password) return t + @command('') def decodetx(self, tx): """Decode serialized transaction""" t = Transaction(tx) return t.deserialize() + @command('n') def sendtx(self, tx): """Broadcast a transaction to the network. """ t = Transaction(tx) return self.network.synchronous_get([('blockchain.transaction.broadcast', [str(t)])])[0] + @command('') def createmultisig(self, num, pubkeys): """Create multisig address""" assert isinstance(pubkeys, list), (type(num), type(pubkeys)) t@@ -162,36 +216,44 @@ class Commands: address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) return {'address':address, 'redeemScript':redeem_script} + @command('w') def freeze(self, address): """Freeze address. Freeze the funds at one of your wallet\'s addresses""" return self.wallet.set_frozen_state([address], True) + @command('w') def unfreeze(self, address): """Unfreeze address. Unfreeze the funds at one of your wallet\'s address""" return self.wallet.set_frozen_state([address], False) + @command('wp') def getprivatekeys(self, address): """Get the private keys of an address. Address must be in wallet.""" return self.wallet.get_private_key(address, self.password) + @command('w') def ismine(self, address): """Check if address is in wallet. Return true if and only address is in wallet""" return self.wallet.is_mine(address) + @command('wp') def dumpprivkeys(self, domain=None): """Dump private keys from your wallet""" if domain is None: domain = self.wallet.addresses(True) return [self.wallet.get_private_key(address, self.password) for address in domain] + @command('') def validateaddress(self, address): """Check that the address is valid. """ return is_address(address) + @command('w') def getpubkeys(self, address): """Return the public keys for a wallet address. """ return self.wallet.get_public_keys(address) + @command('nw') def getbalance(self, account=None): """Return the balance of your wallet""" if account is None: t@@ -205,6 +267,7 @@ class Commands: out["unmatured"] = str(Decimal(x)/100000000) return out + @command('n') def getaddressbalance(self, address): """Return the balance of an address""" out = self.network.synchronous_get([('blockchain.address.get_balance', [address])])[0] t@@ -212,6 +275,7 @@ class Commands: out["unconfirmed"] = str(Decimal(out["unconfirmed"])/100000000) return out + @command('n') def getproof(self, address): """Get Merkle branch of an address in the UTXO set""" p = self.network.synchronous_get([('blockchain.address.get_proof', [address])])[0] t@@ -220,30 +284,36 @@ class Commands: out.append(i) return out + @command('n') def getmerkle(self, txid, height): """Get Merkle branch of a transaction included in a block""" return self.network.synchronous_get([('blockchain.transaction.get_merkle', [txid, int(height)])])[0] + @command('n') def getservers(self): """Return the list of available servers""" while not self.network.is_up_to_date(): time.sleep(0.1) return self.network.get_servers() + @command('') def version(self): """Return the version of electrum.""" import electrum # Needs to stay here to prevent ciruclar imports return electrum.ELECTRUM_VERSION + @command('w') def getmpk(self): """Get Master Public Key. Return your wallet\'s master public key""" return self.wallet.get_master_public_keys() + @command('wp') def getseed(self): """Get seed phrase. Print the generation seed of your wallet.""" s = self.wallet.get_mnemonic(self.password) return s.encode('utf8') + @command('wp') def importprivkey(self, privkey): """Import a private key. """ try: t@@ -253,6 +323,7 @@ class Commands: out = "Error: Keypair import failed: " + str(e) return out + @command('n') def sweep(self, privkey, destination, tx_fee=None, nocheck=False): """Sweep private key. Returns a transaction that spends UTXOs from privkey to a destination address. The transaction is not t@@ -264,11 +335,13 @@ class Commands: fee = int(Decimal(tx_fee)*100000000) return Transaction.sweep([privkey], self.network, dest, fee) + @command('wp') def signmessage(self, address, message): """Sign a message with a key. Use quotes if your message contains whitespaces""" return self.wallet.sign_message(address, message, self.password) + @command('') def verifymessage(self, address, signature, message): """Verify a signature.""" return bitcoin.verify_message(address, signature, message) t@@ -316,12 +389,14 @@ class Commands: outputs.append((address, amount)) return outputs + @command('wp') def mktx(self, destination, amount, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, deserialized=False): """Create a transaction. """ domain = [from_addr] if from_addr else None tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned, deserialized) return tx + @command('wp') def mktx_csv(self, csv_file, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, deserialized=False): """Create a multi-output transaction. """ domain = [from_addr] if from_addr else None t@@ -329,6 +404,7 @@ class Commands: tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned, deserialized) return tx + @command('wpn') def payto(self, destination, amount, tx_fee=None, from_addr=None, change_addr=None, nocheck=False): """Create and broadcast a transaction.. """ domain = [from_addr] if from_addr else None t@@ -336,6 +412,7 @@ class Commands: r, h = self.wallet.sendtx(tx) return h + @command('wpn') def payto_csv(self, csv_file, tx_fee=None, from_addr=None, change_addr=None, nocheck=False): """Create and broadcast multi-output transaction.. """ domain = [from_addr] if from_addr else None t@@ -344,6 +421,7 @@ class Commands: r, h = self.wallet.sendtx(tx) return h + @command('wn') def history(self): """Wallet history. Returns the transaction history of your wallet.""" balance = 0 t@@ -360,19 +438,23 @@ class Commands: out.append({'txid':tx_hash, 'date':"%16s"%time_str, 'label':label, 'value':format_satoshis(value), 'confirmations':conf}) return out + @command('w') def setlabel(self, key, label): """Assign a label to an item. Item may be a bitcoin address or a transaction ID""" self.wallet.set_label(key, label) + @command('') def listcontacts(self): """Show your list of contacts""" return self.contacts + @command('') def getalias(self, key, nocheck=False): """Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record.""" return self.contacts.resolve(key, nocheck) + @command('') def searchcontacts(self, query): """Search through contacts, return matching entries. """ results = {} t@@ -381,6 +463,7 @@ class Commands: results[key] = value return results + @command('w') def listaddresses(self, show_all=False, show_labels=False, frozen=False, unused=False, funded=False, show_balance=False): """List wallet addresses. Returns your list of addresses.""" out = [] t@@ -401,6 +484,7 @@ class Commands: out.append(item) return out + @command('nw') def gettransaction(self, txid, deserialized=False): """Retrieve a transaction. """ tx = self.wallet.transactions.get(txid) if self.wallet else None t@@ -412,101 +496,18 @@ class Commands: raise BaseException("Unknown transaction") return tx.deserialize() if deserialized else tx + @command('') def encrypt(self, pubkey, message): """Encrypt a message with a public key. Use quotes if the message contains whitespaces.""" return bitcoin.encrypt_message(message, pubkey) + @command('wp') def decrypt(self, pubkey, encrypted): """Decrypt a message encrypted with a public key.""" return self.wallet.decrypt_message(pubkey, encrypted, self.password) -class Command: - def __init__(self, name, requires_network, requires_wallet, requires_password): - self.name = name - self.requires_network = bool(requires_network) - self.requires_wallet = bool(requires_wallet) - self.requires_password = bool(requires_password) - # compute params and options - func = getattr(Commands, name) - self.description = func.__doc__ - self.help = self.description.split('.')[0] - varnames = func.func_code.co_varnames[1:func.func_code.co_argcount] - self.defaults = func.func_defaults - if self.defaults: - n = len(self.defaults) - self.params = list(varnames[:-n]) - self.options = list(varnames[-n:]) - else: - self.params = list(varnames) - self.options = [] - self.defaults = [] - - -known_commands = {} - -def register_command(*args): - global known_commands - name = args[0] - known_commands[name] = Command(*args) - - -# command -# requires_network -# requires_wallet -# requires_password -register_command('listcontacts', 0, 0, 0) -register_command('create', 0, 1, 0) -register_command('createmultisig', 0, 1, 0) -register_command('createrawtx', 0, 1, 1) -register_command('deseed', 0, 1, 0) -register_command('decodetx', 0, 0, 0) -register_command('getprivatekeys', 0, 1, 1) -register_command('dumpprivkeys', 0, 1, 1) -register_command('freeze', 0, 1, 0) -register_command('getalias', 0, 0, 0) -register_command('getbalance', 1, 1, 0) -register_command('getservers', 1, 0, 0) -register_command('getaddressbalance', 1, 0, 0) -register_command('getaddresshistory', 1, 0, 0) -register_command('getconfig', 0, 0, 0) -register_command('getpubkeys', 0, 1, 0) -register_command('gettransaction', 1, 0, 0) -register_command('getseed', 0, 1, 1) -register_command('getmpk', 0, 1, 0) -register_command('help', 0, 0, 0) -register_command('history', 1, 1, 0) -register_command('importprivkey', 0, 1, 1) -register_command('ismine', 0, 1, 0) -register_command('listaddresses', 0, 1, 0) -register_command('listunspent', 1, 1, 0) -register_command('getaddressunspent', 1, 0, 0) -register_command('mktx', 0, 1, 1) -register_command('payto', 1, 1, 1) -register_command('mktx_csv', 0, 1, 1) -register_command('payto_csv', 1, 1, 1) -register_command('password', 0, 1, 1) -register_command('restore', 1, 1, 0) -register_command('searchcontacts', 0, 1, 0) -register_command('setconfig', 0, 0, 0) -register_command('setlabel', 0, 1, 0) -register_command('sendtx', 1, 0, 0) -register_command('signtransaction', 0, 1, 1) -register_command('signmessage', 0, 1, 1) -register_command('unfreeze', 0, 1, 0) -register_command('validateaddress', 0, 0, 0) -register_command('verifymessage', 0, 0, 0) -register_command('version', 0, 0, 0) -register_command('encrypt', 0, 0, 0) -register_command('decrypt', 0, 1, 1) -register_command('getmerkle', 1, 0, 0) -register_command('getproof', 1, 0, 0) -register_command('getutxoaddress', 1, 0, 0) -register_command('sweep', 1, 0, 0) -register_command('make_seed', 0, 0, 0) -register_command('check_seed', 0, 0, 0) - param_descriptions = { 'privkey': 'Private key. Type \'?\' to get a prompt.', 'destination': 'Bitcoin address, contact or alias',