diff --git a/mmgen-addrimport b/mmgen-addrimport index dae87be8..d40d7a35 100755 --- a/mmgen-addrimport +++ b/mmgen-addrimport @@ -51,7 +51,7 @@ check_infile(cmd_args[0]) seed_id,addr_data = parse_addrs_file(cmd_args[0]) from mmgen.tx import connect_to_bitcoind -c = connect_to_bitcoind(mmgen=True) +c = connect_to_bitcoind(http_timeout=3600) message = """ Importing addresses can take a long time, up to 30 min. per address on a diff --git a/mmgen-txsend b/mmgen-txsend index df481415..00c383ec 100755 --- a/mmgen-txsend +++ b/mmgen-txsend @@ -83,7 +83,7 @@ confirm_or_exit(warn, what, expect) msg("Sending transaction") -c = connect_to_bitcoind(mmgen=True) +c = connect_to_bitcoind() try: tx = c.sendrawtransaction(tx_sig) diff --git a/mmgen/__init__.py b/mmgen/__init__.py index 76701e79..b5888343 100755 --- a/mmgen/__init__.py +++ b/mmgen/__init__.py @@ -18,8 +18,8 @@ """ MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution """ - __all__ = [ + 'rpc', 'addr.py', 'bitcoin.py', 'config.py', diff --git a/mmgen/connection.py b/mmgen/connection.py deleted file mode 100755 index 5e109a99..00000000 --- a/mmgen/connection.py +++ /dev/null @@ -1,737 +0,0 @@ -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -Connect to Bitcoin server via JSON-RPC. -""" -from mmgen.proxy import JSONRPCException, AuthServiceProxy -from bitcoinrpc.exceptions import (_wrap_exception, WalletPassphraseIncorrect, - WalletAlreadyUnlocked) -from bitcoinrpc.data import (ServerInfo, AccountInfo, AddressInfo, - TransactionInfo, AddressValidation, WorkItem, MiningInfo) - - -class MMGenBitcoinConnection(object): - """ - A BitcoinConnection object defines a connection to a bitcoin server. - It is a thin wrapper around a JSON-RPC API connection. - - Up-to-date for SVN revision 198. - - Arguments to constructor: - - - *user* -- Authenticate as user. - - *password* -- Authentication password. - - *host* -- Bitcoin JSON-RPC host. - - *port* -- Bitcoin JSON-RPC port. - """ - def __init__(self, user, password, host='localhost', port=8332, - use_https=False): - """ - Create a new bitcoin server connection. - """ - url = 'http{s}://{user}:{password}@{host}:{port}/'.format( - s='s' if use_https else '', - user=user, password=password, host=host, port=port) - self.url = url - try: - self.proxy = AuthServiceProxy(url) - except JSONRPCException as e: - raise _wrap_exception(e.error) - -# importaddress
[label] [rescan=true] - def importaddress(self,address,label=None): - """ - """ - try: - return self.proxy.importaddress(address,label) - except JSONRPCException as e: - raise _wrap_exception(e.error) - -# sendrawtransaction [allowhighfees=false] - def sendrawtransaction(self,tx): - """ - """ - try: - return self.proxy.sendrawtransaction(tx) - except JSONRPCException as e: - raise _wrap_exception(e.error) - -# def getbalance(self): -# """ -# Stop bitcoin server. -# """ -# try: -# self.proxy.stop() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# def getblock(self, hash): -# """ -# Returns information about the given block hash. -# """ -# try: -# return self.proxy.getblock(hash) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getblockcount(self): -# """ -# Returns the number of blocks in the longest block chain. -# """ -# try: -# return self.proxy.getblockcount() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getblockhash(self, index): -# """ -# Returns hash of block in best-block-chain at index. -# -# :param index: index ob the block -# -# """ -# try: -# return self.proxy.getblockhash(index) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getblocknumber(self): -# """ -# Returns the block number of the latest block in the longest block chain. -# Deprecated. Use getblockcount instead. -# """ -# return self.getblockcount() -# -# def getconnectioncount(self): -# """ -# Returns the number of connections to other nodes. -# """ -# try: -# return self.proxy.getconnectioncount() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getdifficulty(self): -# """ -# Returns the proof-of-work difficulty as a multiple of the minimum difficulty. -# """ -# try: -# return self.proxy.getdifficulty() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getgenerate(self): -# """ -# Returns :const:`True` or :const:`False`, depending on whether generation is enabled. -# """ -# try: -# return self.proxy.getgenerate() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def setgenerate(self, generate, genproclimit=None): -# """ -# Enable or disable generation (mining) of coins. -# -# Arguments: -# -# - *generate* -- is :const:`True` or :const:`False` to turn generation on or off. -# - *genproclimit* -- Number of processors that are used for generation, -1 is unlimited. -# -# """ -# try: -# if genproclimit is None: -# return self.proxy.setgenerate(generate) -# else: -# return self.proxy.setgenerate(generate, genproclimit) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def gethashespersec(self): -# """ -# Returns a recent hashes per second performance measurement while generating. -# """ -# try: -# return self.proxy.gethashespersec() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getinfo(self): -# """ -# Returns an :class:`~bitcoinrpc.data.ServerInfo` object containing various state info. -# """ -# try: -# return ServerInfo(**self.proxy.getinfo()) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getmininginfo(self): -# """ -# Returns an :class:`~bitcoinrpc.data.MiningInfo` object containing various -# mining state info. -# """ -# try: -# return MiningInfo(**self.proxy.getmininginfo()) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getnewaddress(self, account=None): -# """ -# Returns a new bitcoin address for receiving payments. -# -# Arguments: -# -# - *account* -- If account is specified (recommended), it is added to the address book -# so that payments received with the address will be credited to it. -# -# """ -# try: -# if account is None: -# return self.proxy.getnewaddress() -# else: -# return self.proxy.getnewaddress(account) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getaccountaddress(self, account): -# """ -# Returns the current bitcoin address for receiving payments to an account. -# -# Arguments: -# -# - *account* -- Account for which the address should be returned. -# -# """ -# try: -# return self.proxy.getaccountaddress(account) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def setaccount(self, bitcoinaddress, account): -# """ -# Sets the account associated with the given address. -# -# Arguments: -# -# - *bitcoinaddress* -- Bitcoin address to associate. -# - *account* -- Account to associate the address to. -# -# """ -# try: -# return self.proxy.setaccount(bitcoinaddress, account) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getaccount(self, bitcoinaddress): -# """ -# Returns the account associated with the given address. -# -# Arguments: -# -# - *bitcoinaddress* -- Bitcoin address to get account for. -# """ -# try: -# return self.proxy.getaccount(bitcoinaddress) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getaddressesbyaccount(self, account): -# """ -# Returns the list of addresses for the given account. -# -# Arguments: -# -# - *account* -- Account to get list of addresses for. -# """ -# try: -# return self.proxy.getaddressesbyaccount(account) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def sendtoaddress(self, bitcoinaddress, amount, comment=None, comment_to=None): -# """ -# Sends *amount* from the server's available balance to *bitcoinaddress*. -# -# Arguments: -# -# - *bitcoinaddress* -- Bitcoin address to send to. -# - *amount* -- Amount to send (float, rounded to the nearest 0.01). -# - *minconf* -- Minimum number of confirmations required for transferred balance. -# - *comment* -- Comment for transaction. -# - *comment_to* -- Comment for to-address. -# -# """ -# try: -# if comment is None: -# return self.proxy.sendtoaddress(bitcoinaddress, amount) -# elif comment_to is None: -# return self.proxy.sendtoaddress(bitcoinaddress, amount, comment) -# else: -# return self.proxy.sendtoaddress(bitcoinaddress, amount, comment, comment_to) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getreceivedbyaddress(self, bitcoinaddress, minconf=1): -# """ -# Returns the total amount received by a bitcoin address in transactions with at least a -# certain number of confirmations. -# -# Arguments: -# -# - *bitcoinaddress* -- Address to query for total amount. -# -# - *minconf* -- Number of confirmations to require, defaults to 1. -# """ -# try: -# return self.proxy.getreceivedbyaddress(bitcoinaddress, minconf) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getreceivedbyaccount(self, account, minconf=1): -# """ -# Returns the total amount received by addresses with an account in transactions with -# at least a certain number of confirmations. -# -# Arguments: -# -# - *account* -- Account to query for total amount. -# - *minconf* -- Number of confirmations to require, defaults to 1. -# -# """ -# try: -# return self.proxy.getreceivedbyaccount(account, minconf) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def gettransaction(self, txid): -# """ -# Get detailed information about transaction -# -# Arguments: -# -# - *txid* -- Transactiond id for which the info should be returned -# -# """ -# try: -# return TransactionInfo(**self.proxy.gettransaction(txid)) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getrawtransaction(self, txid, verbose=True): -# """ -# Get transaction raw info -# -# Arguments: -# -# - *txid* -- Transactiond id for which the info should be returned. -# - *verbose* -- If False, return only the "hex" of the transaction. -# -# """ -# try: -# if verbose: -# return TransactionInfo(**self.proxy.getrawtransaction(txid, 1)) -# return self.proxy.getrawtransaction(txid, 0) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def createrawtransaction(self, inputs, outputs): -# """ -# Creates a raw transaction spending given inputs -# (a list of dictionaries, each containing a transaction id and an output number), -# sending to given address(es). -# -# Returns hex-encoded raw transaction. -# -# Example usage: -# >>> conn.createrawtransaction( -# [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c", -# "vout": 0}], -# {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50}) -# -# -# Arguments: -# -# - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries. -# - *outputs* -- A dictionary mapping (public) addresses to the amount -# they are to be paid. -# """ -# try: -# return self.proxy.createrawtransaction(inputs, outputs) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def signrawtransaction(self, hexstring, previous_transactions=None, private_keys=None): -# """ -# Sign inputs for raw transaction (serialized, hex-encoded). -# -# Returns a dictionary with the keys: -# "hex": raw transaction with signature(s) (hex-encoded string) -# "complete": 1 if transaction has a complete set of signature(s), 0 if not -# -# Arguments: -# -# - *hexstring* -- A hex string of the transaction to sign. -# - *previous_transactions* -- A (possibly empty) list of dictionaries of the form: -# {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex}, representing -# previous transaction outputs that this transaction depends on but may not yet be -# in the block chain. -# - *private_keys* -- A (possibly empty) list of base58-encoded private -# keys that, if given, will be the only keys used to sign the transaction. -# """ -# try: -# return dict(self.proxy.signrawtransaction(hexstring, previous_transactions, private_keys)) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def decoderawtransaction(self, hexstring): -# """ -# Produces a human-readable JSON object for a raw transaction. -# -# Arguments: -# -# - *hexstring* -- A hex string of the transaction to be decoded. -# """ -# try: -# return dict(self.proxy.decoderawtransaction(hexstring)) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def listsinceblock(self, block_hash): -# try: -# res = self.proxy.listsinceblock(block_hash) -# res['transactions'] = [TransactionInfo(**x) for x in res['transactions']] -# return res -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def listreceivedbyaddress(self, minconf=1, includeempty=False): -# """ -# Returns a list of addresses. -# -# Each address is represented with a :class:`~bitcoinrpc.data.AddressInfo` object. -# -# Arguments: -# -# - *minconf* -- Minimum number of confirmations before payments are included. -# - *includeempty* -- Whether to include addresses that haven't received any payments. -# -# """ -# try: -# return [AddressInfo(**x) for x in -# self.proxy.listreceivedbyaddress(minconf, includeempty)] -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def listaccounts(self, minconf=1, as_dict=False): -# """ -# Returns a list of account names. -# -# Arguments: -# -# - *minconf* -- Minimum number of confirmations before payments are included. -# - *as_dict* -- Returns a dictionary of account names, with their balance as values. -# """ -# try: -# if as_dict: -# return dict(self.proxy.listaccounts(minconf)) -# else: -# return self.proxy.listaccounts(minconf).keys() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def listreceivedbyaccount(self, minconf=1, includeempty=False): -# """ -# Returns a list of accounts. -# -# Each account is represented with a :class:`~bitcoinrpc.data.AccountInfo` object. -# -# Arguments: -# -# - *minconf* -- Minimum number of confirmations before payments are included. -# -# - *includeempty* -- Whether to include addresses that haven't received any payments. -# """ -# try: -# return [AccountInfo(**x) for x in -# self.proxy.listreceivedbyaccount(minconf, includeempty)] -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def listtransactions(self, account=None, count=10, from_=0, address=None): -# """ -# Returns a list of the last transactions for an account. -# -# Each transaction is represented with a :class:`~bitcoinrpc.data.TransactionInfo` object. -# -# Arguments: -# -# - *account* -- Account to list transactions from. Return transactions from -# all accounts if None. -# - *count* -- Number of transactions to return. -# - *from_* -- Skip the first transactions. -# - *address* -- Receive address to consider -# """ -# accounts = [account] if account is not None else self.listaccounts(as_dict=True).iterkeys() -# try: -# return [TransactionInfo(**tx) for acc in accounts for -# tx in self.proxy.listtransactions(acc, count, from_) if -# address is None or tx["address"] == address] -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def backupwallet(self, destination): -# """ -# Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path -# with filename. -# -# Arguments: -# - *destination* -- directory or path with filename to backup wallet to. -# -# """ -# try: -# return self.proxy.backupwallet(destination) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def validateaddress(self, validateaddress): -# """ -# Validate a bitcoin address and return information for it. -# -# The information is represented by a :class:`~bitcoinrpc.data.AddressValidation` object. -# -# Arguments: -- Address to validate. -# -# -# - *validateaddress* -# """ -# try: -# return AddressValidation(**self.proxy.validateaddress(validateaddress)) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getbalance(self, account=None, minconf=None): -# """ -# Get the current balance, either for an account or the total server balance. -# -# Arguments: -# - *account* -- If this parameter is specified, returns the balance in the account. -# - *minconf* -- Minimum number of confirmations required for transferred balance. -# -# """ -# args = [] -# if account: -# args.append(account) -# if minconf is not None: -# args.append(minconf) -# try: -# return self.proxy.getbalance(*args) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def move(self, fromaccount, toaccount, amount, minconf=1, comment=None): -# """ -# Move from one account in your wallet to another. -# -# Arguments: -# -# - *fromaccount* -- Source account name. -# - *toaccount* -- Destination account name. -# - *amount* -- Amount to transfer. -# - *minconf* -- Minimum number of confirmations required for transferred balance. -# - *comment* -- Comment to add to transaction log. -# -# """ -# try: -# if comment is None: -# return self.proxy.move(fromaccount, toaccount, amount, minconf) -# else: -# return self.proxy.move(fromaccount, toaccount, amount, minconf, comment) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def sendfrom(self, fromaccount, tobitcoinaddress, amount, minconf=1, comment=None, -# comment_to=None): -# """ -# Sends amount from account's balance to bitcoinaddress. This method will fail -# if there is less than amount bitcoins with minconf confirmations in the account's -# balance (unless account is the empty-string-named default account; it -# behaves like the sendtoaddress method). Returns transaction ID on success. -# -# Arguments: -# -# - *fromaccount* -- Account to send from. -# - *tobitcoinaddress* -- Bitcoin address to send to. -# - *amount* -- Amount to send (float, rounded to the nearest 0.01). -# - *minconf* -- Minimum number of confirmations required for transferred balance. -# - *comment* -- Comment for transaction. -# - *comment_to* -- Comment for to-address. -# -# """ -# try: -# if comment is None: -# return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf) -# elif comment_to is None: -# return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, comment) -# else: -# return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, -# comment, comment_to) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def sendmany(self, fromaccount, todict, minconf=1, comment=None): -# """ -# Sends specified amounts from account's balance to bitcoinaddresses. This method will fail -# if there is less than total amount bitcoins with minconf confirmations in the account's -# balance (unless account is the empty-string-named default account; Returns transaction ID -# on success. -# -# Arguments: -# -# - *fromaccount* -- Account to send from. -# - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as values. -# - *minconf* -- Minimum number of confirmations required for transferred balance. -# - *comment* -- Comment for transaction. -# -# """ -# try: -# if comment is None: -# return self.proxy.sendmany(fromaccount, todict, minconf) -# else: -# return self.proxy.sendmany(fromaccount, todict, minconf, comment) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def verifymessage(self, bitcoinaddress, signature, message): -# """ -# Verifies a signature given the bitcoinaddress used to sign, -# the signature itself, and the message that was signed. -# Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid. -# -# Arguments: -# -# - *bitcoinaddress* -- the bitcoinaddress used to sign the message -# - *signature* -- the signature to be verified -# - *message* -- the message that was originally signed -# -# """ -# try: -# return self.proxy.verifymessage(bitcoinaddress, signature, message) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def getwork(self, data=None): -# """ -# Get work for remote mining, or submit result. -# If data is specified, the server tries to solve the block -# using the provided data and returns :const:`True` if it was successful. -# If not, the function returns formatted hash data (:class:`~bitcoinrpc.data.WorkItem`) -# to work on. -# -# Arguments: -# -# - *data* -- Result from remote mining. -# -# """ -# try: -# if data is None: -# # Only if no data provided, it returns a WorkItem -# return WorkItem(**self.proxy.getwork()) -# else: -# return self.proxy.getwork(data) -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def listunspent(self, minconf=1, maxconf=999999): -# """ -# Returns a list of unspent transaction inputs in the wallet. -# -# Arguments: -# -# - *minconf* -- Minimum number of confirmations required to be listed. -# -# - *maxconf* -- Maximal number of confirmations allowed to be listed. -# -# -# """ -# try: -# return [TransactionInfo(**tx) for tx in -# self.proxy.listunspent(minconf, maxconf)] -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def keypoolrefill(self): -# "Fills the keypool, requires wallet passphrase to be set." -# try: -# self.proxy.keypoolrefill() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def walletpassphrase(self, passphrase, timeout, dont_raise=False): -# """ -# Stores the wallet decryption key in memory for seconds. -# -# - *passphrase* -- The wallet passphrase. -# -# - *timeout* -- Time in seconds to keep the wallet unlocked -# (by keeping the passphrase in memory). -# -# - *dont_raise* -- instead of raising `~bitcoinrpc.exceptions.WalletPassphraseIncorrect` -# return False. -# """ -# try: -# self.proxy.walletpassphrase(passphrase, timeout) -# return True -# except JSONRPCException as e: -# json_exception = _wrap_exception(e.error) -# if dont_raise: -# if isinstance(json_exception, WalletPassphraseIncorrect): -# return False -# elif isinstance(json_exception, WalletAlreadyUnlocked): -# return True -# raise json_exception -# -# def walletlock(self): -# """ -# Removes the wallet encryption key from memory, locking the wallet. -# After calling this method, you will need to call walletpassphrase -# again before being able to call any methods which require the wallet -# to be unlocked. -# """ -# try: -# return self.proxy.walletlock() -# except JSONRPCException as e: -# raise _wrap_exception(e.error) -# -# def walletpassphrasechange(self, oldpassphrase, newpassphrase, dont_raise=False): -# """ -# Changes the wallet passphrase from to . -# -# Arguments: -# -# - *dont_raise* -- instead of raising `~bitcoinrpc.exceptions.WalletPassphraseIncorrect` -# return False. -# """ -# try: -# self.proxy.walletpassphrasechange(oldpassphrase, newpassphrase) -# return True -# except JSONRPCException as e: -# json_exception = _wrap_exception(e.error) -# if dont_raise and isinstance(json_exception, WalletPassphraseIncorrect): -# return False -# raise json_exception diff --git a/mmgen/rpc/__init__.py b/mmgen/rpc/__init__.py new file mode 100755 index 00000000..3dc3a5c9 --- /dev/null +++ b/mmgen/rpc/__init__.py @@ -0,0 +1,54 @@ +# Copyright (c) 2010 Witchspace +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +bitcoin-python - Easy-to-use Bitcoin API client +""" + + +def connect_to_local(filename=None): + """ + Connect to default bitcoin instance owned by this user, on this machine. + + Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object. + + Arguments: + + - `filename`: Path to a configuration file in a non-standard location (optional) + """ + from mmgen.rpc.connection import BitcoinConnection + from mmgen.rpc.config import read_default_config + + cfg = read_default_config(filename) + port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332')) + rcpuser = cfg.get('rpcuser', '') + + return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port) + + +def connect_to_remote(user, password, host='localhost', port=8332, + use_https=False): + """ + Connect to remote or alternative local bitcoin client instance. + + Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object. + """ + from mmgen.rpc.connection import BitcoinConnection + + return BitcoinConnection(user, password, host, port, use_https) diff --git a/mmgen/rpc/config.py b/mmgen/rpc/config.py new file mode 100755 index 00000000..6110c153 --- /dev/null +++ b/mmgen/rpc/config.py @@ -0,0 +1,75 @@ +# Copyright (c) 2010 Witchspace +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +Utilities for reading bitcoin configuration files. +""" + + +def read_config_file(filename): + """ + Read a simple ``'='``-delimited config file. + Raises :const:`IOError` if unable to open file, or :const:`ValueError` + if an parse error occurs. + """ + f = open(filename) + try: + cfg = {} + for line in f: + line = line.strip() + if line and not line.startswith("#"): + try: + (key, value) = line.split('=', 1) + cfg[key] = value + except ValueError: + pass # Happens when line has no '=', ignore + finally: + f.close() + return cfg + + +def read_default_config(filename=None): + """ + Read bitcoin default configuration from the current user's home directory. + + Arguments: + + - `filename`: Path to a configuration file in a non-standard location (optional) + """ + if filename is None: + import os + import platform + home = os.getenv("HOME") + if not home: + raise IOError("Home directory not defined, don't know where to look for config file") + + if platform.system() == "Darwin": + location = 'Library/Application Support/Bitcoin/bitcoin.conf' + else: + location = '.bitcoin/bitcoin.conf' + filename = os.path.join(home, location) + + elif filename.startswith("~"): + import os + filename = os.path.expanduser(filename) + + try: + return read_config_file(filename) + except (IOError, ValueError): + pass # Cannot read config file, ignore diff --git a/mmgen/rpc/connection.py b/mmgen/rpc/connection.py new file mode 100755 index 00000000..91881713 --- /dev/null +++ b/mmgen/rpc/connection.py @@ -0,0 +1,743 @@ +# Copyright (C) 2013 by philemon +# Added configurable http_timeout and methods for +# sendrawtransaction, +# importaddress +# +# Previous copyright from bitcoin-python/connection.py: +# Copyright (c) 2010 Witchspace +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +Connect to Bitcoin server via JSON-RPC. +""" +from mmgen.rpc.proxy import JSONRPCException, AuthServiceProxy +from mmgen.rpc.exceptions import _wrap_exception, WalletPassphraseIncorrect, WalletAlreadyUnlocked +from mmgen.rpc.data import (ServerInfo, AccountInfo, AddressInfo, TransactionInfo, + AddressValidation, WorkItem, MiningInfo) + + +class BitcoinConnection(object): + """ + A BitcoinConnection object defines a connection to a bitcoin server. + It is a thin wrapper around a JSON-RPC API connection. + + Up-to-date for SVN revision 198. + + Arguments to constructor: + + - *user* -- Authenticate as user. + - *password* -- Authentication password. + - *host* -- Bitcoin JSON-RPC host. + - *port* -- Bitcoin JSON-RPC port. + """ + def __init__(self, user, password, host='localhost', port=8332, + use_https=False,http_timeout=30): + """ + Create a new bitcoin server connection. + """ + url = 'http{s}://{user}:{password}@{host}:{port}/'.format( + s='s' if use_https else '', + user=user, password=password, host=host, port=port) + self.url = url + try: + self.proxy = AuthServiceProxy(url,http_timeout=http_timeout) + except JSONRPCException as e: + raise _wrap_exception(e.error) + +# importaddress
[label] [rescan=true] + def importaddress(self,address,label=None): + """ + """ + try: + return self.proxy.importaddress(address,label) + except JSONRPCException as e: + raise _wrap_exception(e.error) + +# sendrawtransaction [allowhighfees=false] + def sendrawtransaction(self,tx): + """ + """ + try: + return self.proxy.sendrawtransaction(tx) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def stop(self): + """ + Stop bitcoin server. + """ + try: + self.proxy.stop() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getblock(self, hash): + """ + Returns information about the given block hash. + """ + try: + return self.proxy.getblock(hash) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getblockcount(self): + """ + Returns the number of blocks in the longest block chain. + """ + try: + return self.proxy.getblockcount() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getblockhash(self, index): + """ + Returns hash of block in best-block-chain at index. + + :param index: index ob the block + + """ + try: + return self.proxy.getblockhash(index) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getblocknumber(self): + """ + Returns the block number of the latest block in the longest block chain. + Deprecated. Use getblockcount instead. + """ + return self.getblockcount() + + def getconnectioncount(self): + """ + Returns the number of connections to other nodes. + """ + try: + return self.proxy.getconnectioncount() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getdifficulty(self): + """ + Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + """ + try: + return self.proxy.getdifficulty() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getgenerate(self): + """ + Returns :const:`True` or :const:`False`, depending on whether generation is enabled. + """ + try: + return self.proxy.getgenerate() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def setgenerate(self, generate, genproclimit=None): + """ + Enable or disable generation (mining) of coins. + + Arguments: + + - *generate* -- is :const:`True` or :const:`False` to turn generation on or off. + - *genproclimit* -- Number of processors that are used for generation, -1 is unlimited. + + """ + try: + if genproclimit is None: + return self.proxy.setgenerate(generate) + else: + return self.proxy.setgenerate(generate, genproclimit) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def gethashespersec(self): + """ + Returns a recent hashes per second performance measurement while generating. + """ + try: + return self.proxy.gethashespersec() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getinfo(self): + """ + Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing various state info. + """ + try: + return ServerInfo(**self.proxy.getinfo()) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getmininginfo(self): + """ + Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various + mining state info. + """ + try: + return MiningInfo(**self.proxy.getmininginfo()) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getnewaddress(self, account=None): + """ + Returns a new bitcoin address for receiving payments. + + Arguments: + + - *account* -- If account is specified (recommended), it is added to the address book + so that payments received with the address will be credited to it. + + """ + try: + if account is None: + return self.proxy.getnewaddress() + else: + return self.proxy.getnewaddress(account) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getaccountaddress(self, account): + """ + Returns the current bitcoin address for receiving payments to an account. + + Arguments: + + - *account* -- Account for which the address should be returned. + + """ + try: + return self.proxy.getaccountaddress(account) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def setaccount(self, bitcoinaddress, account): + """ + Sets the account associated with the given address. + + Arguments: + + - *bitcoinaddress* -- Bitcoin address to associate. + - *account* -- Account to associate the address to. + + """ + try: + return self.proxy.setaccount(bitcoinaddress, account) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getaccount(self, bitcoinaddress): + """ + Returns the account associated with the given address. + + Arguments: + + - *bitcoinaddress* -- Bitcoin address to get account for. + """ + try: + return self.proxy.getaccount(bitcoinaddress) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getaddressesbyaccount(self, account): + """ + Returns the list of addresses for the given account. + + Arguments: + + - *account* -- Account to get list of addresses for. + """ + try: + return self.proxy.getaddressesbyaccount(account) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def sendtoaddress(self, bitcoinaddress, amount, comment=None, comment_to=None): + """ + Sends *amount* from the server's available balance to *bitcoinaddress*. + + Arguments: + + - *bitcoinaddress* -- Bitcoin address to send to. + - *amount* -- Amount to send (float, rounded to the nearest 0.01). + - *minconf* -- Minimum number of confirmations required for transferred balance. + - *comment* -- Comment for transaction. + - *comment_to* -- Comment for to-address. + + """ + try: + if comment is None: + return self.proxy.sendtoaddress(bitcoinaddress, amount) + elif comment_to is None: + return self.proxy.sendtoaddress(bitcoinaddress, amount, comment) + else: + return self.proxy.sendtoaddress(bitcoinaddress, amount, comment, comment_to) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getreceivedbyaddress(self, bitcoinaddress, minconf=1): + """ + Returns the total amount received by a bitcoin address in transactions with at least a + certain number of confirmations. + + Arguments: + + - *bitcoinaddress* -- Address to query for total amount. + + - *minconf* -- Number of confirmations to require, defaults to 1. + """ + try: + return self.proxy.getreceivedbyaddress(bitcoinaddress, minconf) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getreceivedbyaccount(self, account, minconf=1): + """ + Returns the total amount received by addresses with an account in transactions with + at least a certain number of confirmations. + + Arguments: + + - *account* -- Account to query for total amount. + - *minconf* -- Number of confirmations to require, defaults to 1. + + """ + try: + return self.proxy.getreceivedbyaccount(account, minconf) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def gettransaction(self, txid): + """ + Get detailed information about transaction + + Arguments: + + - *txid* -- Transactiond id for which the info should be returned + + """ + try: + return TransactionInfo(**self.proxy.gettransaction(txid)) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getrawtransaction(self, txid, verbose=True): + """ + Get transaction raw info + + Arguments: + + - *txid* -- Transactiond id for which the info should be returned. + - *verbose* -- If False, return only the "hex" of the transaction. + + """ + try: + if verbose: + return TransactionInfo(**self.proxy.getrawtransaction(txid, 1)) + return self.proxy.getrawtransaction(txid, 0) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def createrawtransaction(self, inputs, outputs): + """ + Creates a raw transaction spending given inputs + (a list of dictionaries, each containing a transaction id and an output number), + sending to given address(es). + + Returns hex-encoded raw transaction. + + Example usage: + >>> conn.createrawtransaction( + [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c", + "vout": 0}], + {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50}) + + + Arguments: + + - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries. + - *outputs* -- A dictionary mapping (public) addresses to the amount + they are to be paid. + """ + try: + return self.proxy.createrawtransaction(inputs, outputs) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def signrawtransaction(self, hexstring, previous_transactions=None, private_keys=None): + """ + Sign inputs for raw transaction (serialized, hex-encoded). + + Returns a dictionary with the keys: + "hex": raw transaction with signature(s) (hex-encoded string) + "complete": 1 if transaction has a complete set of signature(s), 0 if not + + Arguments: + + - *hexstring* -- A hex string of the transaction to sign. + - *previous_transactions* -- A (possibly empty) list of dictionaries of the form: + {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex}, representing + previous transaction outputs that this transaction depends on but may not yet be + in the block chain. + - *private_keys* -- A (possibly empty) list of base58-encoded private + keys that, if given, will be the only keys used to sign the transaction. + """ + try: + return dict(self.proxy.signrawtransaction(hexstring, previous_transactions, private_keys)) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def decoderawtransaction(self, hexstring): + """ + Produces a human-readable JSON object for a raw transaction. + + Arguments: + + - *hexstring* -- A hex string of the transaction to be decoded. + """ + try: + return dict(self.proxy.decoderawtransaction(hexstring)) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def listsinceblock(self, block_hash): + try: + res = self.proxy.listsinceblock(block_hash) + res['transactions'] = [TransactionInfo(**x) for x in res['transactions']] + return res + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def listreceivedbyaddress(self, minconf=1, includeempty=False): + """ + Returns a list of addresses. + + Each address is represented with a :class:`~mmgen.rpc.data.AddressInfo` object. + + Arguments: + + - *minconf* -- Minimum number of confirmations before payments are included. + - *includeempty* -- Whether to include addresses that haven't received any payments. + + """ + try: + return [AddressInfo(**x) for x in + self.proxy.listreceivedbyaddress(minconf, includeempty)] + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def listaccounts(self, minconf=1, as_dict=False): + """ + Returns a list of account names. + + Arguments: + + - *minconf* -- Minimum number of confirmations before payments are included. + - *as_dict* -- Returns a dictionary of account names, with their balance as values. + """ + try: + if as_dict: + return dict(self.proxy.listaccounts(minconf)) + else: + return self.proxy.listaccounts(minconf).keys() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def listreceivedbyaccount(self, minconf=1, includeempty=False): + """ + Returns a list of accounts. + + Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object. + + Arguments: + + - *minconf* -- Minimum number of confirmations before payments are included. + + - *includeempty* -- Whether to include addresses that haven't received any payments. + """ + try: + return [AccountInfo(**x) for x in + self.proxy.listreceivedbyaccount(minconf, includeempty)] + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def listtransactions(self, account=None, count=10, from_=0, address=None): + """ + Returns a list of the last transactions for an account. + + Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object. + + Arguments: + + - *account* -- Account to list transactions from. Return transactions from + all accounts if None. + - *count* -- Number of transactions to return. + - *from_* -- Skip the first transactions. + - *address* -- Receive address to consider + """ + accounts = [account] if account is not None else self.listaccounts(as_dict=True).iterkeys() + try: + return [TransactionInfo(**tx) for acc in accounts for + tx in self.proxy.listtransactions(acc, count, from_) if + address is None or tx["address"] == address] + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def backupwallet(self, destination): + """ + Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path + with filename. + + Arguments: + - *destination* -- directory or path with filename to backup wallet to. + + """ + try: + return self.proxy.backupwallet(destination) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def validateaddress(self, validateaddress): + """ + Validate a bitcoin address and return information for it. + + The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object. + + Arguments: -- Address to validate. + + + - *validateaddress* + """ + try: + return AddressValidation(**self.proxy.validateaddress(validateaddress)) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getbalance(self, account=None, minconf=None): + """ + Get the current balance, either for an account or the total server balance. + + Arguments: + - *account* -- If this parameter is specified, returns the balance in the account. + - *minconf* -- Minimum number of confirmations required for transferred balance. + + """ + args = [] + if account: + args.append(account) + if minconf is not None: + args.append(minconf) + try: + return self.proxy.getbalance(*args) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def move(self, fromaccount, toaccount, amount, minconf=1, comment=None): + """ + Move from one account in your wallet to another. + + Arguments: + + - *fromaccount* -- Source account name. + - *toaccount* -- Destination account name. + - *amount* -- Amount to transfer. + - *minconf* -- Minimum number of confirmations required for transferred balance. + - *comment* -- Comment to add to transaction log. + + """ + try: + if comment is None: + return self.proxy.move(fromaccount, toaccount, amount, minconf) + else: + return self.proxy.move(fromaccount, toaccount, amount, minconf, comment) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def sendfrom(self, fromaccount, tobitcoinaddress, amount, minconf=1, comment=None, + comment_to=None): + """ + Sends amount from account's balance to bitcoinaddress. This method will fail + if there is less than amount bitcoins with minconf confirmations in the account's + balance (unless account is the empty-string-named default account; it + behaves like the sendtoaddress method). Returns transaction ID on success. + + Arguments: + + - *fromaccount* -- Account to send from. + - *tobitcoinaddress* -- Bitcoin address to send to. + - *amount* -- Amount to send (float, rounded to the nearest 0.01). + - *minconf* -- Minimum number of confirmations required for transferred balance. + - *comment* -- Comment for transaction. + - *comment_to* -- Comment for to-address. + + """ + try: + if comment is None: + return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf) + elif comment_to is None: + return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, comment) + else: + return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, + comment, comment_to) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def sendmany(self, fromaccount, todict, minconf=1, comment=None): + """ + Sends specified amounts from account's balance to bitcoinaddresses. This method will fail + if there is less than total amount bitcoins with minconf confirmations in the account's + balance (unless account is the empty-string-named default account; Returns transaction ID + on success. + + Arguments: + + - *fromaccount* -- Account to send from. + - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as values. + - *minconf* -- Minimum number of confirmations required for transferred balance. + - *comment* -- Comment for transaction. + + """ + try: + if comment is None: + return self.proxy.sendmany(fromaccount, todict, minconf) + else: + return self.proxy.sendmany(fromaccount, todict, minconf, comment) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def verifymessage(self, bitcoinaddress, signature, message): + """ + Verifies a signature given the bitcoinaddress used to sign, + the signature itself, and the message that was signed. + Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid. + + Arguments: + + - *bitcoinaddress* -- the bitcoinaddress used to sign the message + - *signature* -- the signature to be verified + - *message* -- the message that was originally signed + + """ + try: + return self.proxy.verifymessage(bitcoinaddress, signature, message) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def getwork(self, data=None): + """ + Get work for remote mining, or submit result. + If data is specified, the server tries to solve the block + using the provided data and returns :const:`True` if it was successful. + If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`) + to work on. + + Arguments: + + - *data* -- Result from remote mining. + + """ + try: + if data is None: + # Only if no data provided, it returns a WorkItem + return WorkItem(**self.proxy.getwork()) + else: + return self.proxy.getwork(data) + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def listunspent(self, minconf=1, maxconf=999999): + """ + Returns a list of unspent transaction inputs in the wallet. + + Arguments: + + - *minconf* -- Minimum number of confirmations required to be listed. + + - *maxconf* -- Maximal number of confirmations allowed to be listed. + + + """ + try: + return [TransactionInfo(**tx) for tx in + self.proxy.listunspent(minconf, maxconf)] + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def keypoolrefill(self): + "Fills the keypool, requires wallet passphrase to be set." + try: + self.proxy.keypoolrefill() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def walletpassphrase(self, passphrase, timeout, dont_raise=False): + """ + Stores the wallet decryption key in memory for seconds. + + - *passphrase* -- The wallet passphrase. + + - *timeout* -- Time in seconds to keep the wallet unlocked + (by keeping the passphrase in memory). + + - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` + return False. + """ + try: + self.proxy.walletpassphrase(passphrase, timeout) + return True + except JSONRPCException as e: + json_exception = _wrap_exception(e.error) + if dont_raise: + if isinstance(json_exception, WalletPassphraseIncorrect): + return False + elif isinstance(json_exception, WalletAlreadyUnlocked): + return True + raise json_exception + + def walletlock(self): + """ + Removes the wallet encryption key from memory, locking the wallet. + After calling this method, you will need to call walletpassphrase + again before being able to call any methods which require the wallet + to be unlocked. + """ + try: + return self.proxy.walletlock() + except JSONRPCException as e: + raise _wrap_exception(e.error) + + def walletpassphrasechange(self, oldpassphrase, newpassphrase, dont_raise=False): + """ + Changes the wallet passphrase from to . + + Arguments: + + - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` + return False. + """ + try: + self.proxy.walletpassphrasechange(oldpassphrase, newpassphrase) + return True + except JSONRPCException as e: + json_exception = _wrap_exception(e.error) + if dont_raise and isinstance(json_exception, WalletPassphraseIncorrect): + return False + raise json_exception diff --git a/mmgen/rpc/data.py b/mmgen/rpc/data.py new file mode 100755 index 00000000..b8577b2c --- /dev/null +++ b/mmgen/rpc/data.py @@ -0,0 +1,168 @@ +# Copyright (c) 2010 Witchspace +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +Bitcoin RPC service, data objects. +""" +from mmgen.rpc.util import DStruct + + +class ServerInfo(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getinfo`. + + - *errors* -- Number of errors. + + - *blocks* -- Number of blocks. + + - *paytxfee* -- Amount of transaction fee to pay. + + - *keypoololdest* -- Oldest key in keypool. + + - *genproclimit* -- Processor limit for generation. + + - *connections* -- Number of connections to other clients. + + - *difficulty* -- Current generating difficulty. + + - *testnet* -- True if connected to testnet, False if on real network. + + - *version* -- Bitcoin client version. + + - *proxy* -- Proxy configured in client. + + - *hashespersec* -- Number of hashes per second (if generation enabled). + + - *balance* -- Total current server balance. + + - *generate* -- True if generation enabled, False if not. + + - *unlocked_until* -- Timestamp (seconds since epoch) after which the wallet + will be/was locked (if wallet encryption is enabled). + + """ + + +class AccountInfo(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listreceivedbyaccount`. + + - *account* -- The account of the receiving address. + + - *amount* -- Total amount received by the address. + + - *confirmations* -- Number of confirmations of the most recent transaction included. + + """ + + +class AddressInfo(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listreceivedbyaddress`. + + - *address* -- Receiving address. + + - *account* -- The account of the receiving address. + + - *amount* -- Total amount received by the address. + + - *confirmations* -- Number of confirmations of the most recent transaction included. + + """ + + +class TransactionInfo(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listtransactions`. + + - *account* -- account name. + + - *address* -- the address bitcoins were sent to, or received from. + + - *category* -- will be generate, send, receive, or move. + + - *amount* -- amount of transaction. + + - *fee* -- Fee (if any) paid (only for send transactions). + + - *confirmations* -- number of confirmations (only for generate/send/receive). + + - *txid* -- transaction ID (only for generate/send/receive). + + - *otheraccount* -- account funds were moved to or from (only for move). + + - *message* -- message associated with transaction (only for send). + + - *to* -- message-to associated with transaction (only for send). + """ + + +class AddressValidation(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.validateaddress`. + + - *isvalid* -- Validatity of address (:const:`True` or :const:`False`). + + - *ismine* -- :const:`True` if the address is in the server's wallet. + + - *address* -- Bitcoin address. + + """ + + +class WorkItem(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getwork`. + + - *midstate* -- Precomputed hash state after hashing the first half of the data. + + - *data* -- Block data. + + - *hash1* -- Formatted hash buffer for second hash. + + - *target* -- Little endian hash target. + + """ + + +class MiningInfo(DStruct): + """ + Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getmininginfo`. + + - *blocks* -- Number of blocks. + + - *currentblocksize* -- Size of current block. + + - *currentblocktx* -- Number of transactions in current block. + + - *difficulty* -- Current generating difficulty. + + - *errors* -- Number of errors. + + - *generate* -- True if generation enabled, False if not. + + - *genproclimit* -- Processor limit for generation. + + - *hashespersec* -- Number of hashes per second (if generation enabled). + + - *pooledtx* -- Number of pooled transactions. + + - *testnet* -- True if connected to testnet, False if on real network. + + """ diff --git a/mmgen/rpc/exceptions.py b/mmgen/rpc/exceptions.py new file mode 100755 index 00000000..59189541 --- /dev/null +++ b/mmgen/rpc/exceptions.py @@ -0,0 +1,203 @@ +# Copyright (c) 2010 Witchspace +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +Exception definitions. +""" + + +class BitcoinException(Exception): + """ + Base class for exceptions received from Bitcoin server. + + - *code* -- Error code from ``bitcoind``. + """ + # Standard JSON-RPC 2.0 errors + INVALID_REQUEST = -32600, + METHOD_NOT_FOUND = -32601, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, + PARSE_ERROR = -32700, + + # General application defined errors + MISC_ERROR = -1 # std::exception thrown in command handling + FORBIDDEN_BY_SAFE_MODE = -2 # Server is in safe mode, and command is not allowed in safe mode + TYPE_ERROR = -3 # Unexpected type was passed as parameter + INVALID_ADDRESS_OR_KEY = -5 # Invalid address or key + OUT_OF_MEMORY = -7 # Ran out of memory during operation + INVALID_PARAMETER = -8 # Invalid, missing or duplicate parameter + DATABASE_ERROR = -20 # Database error + DESERIALIZATION_ERROR = -22 # Error parsing or validating structure in raw format + + # P2P client errors + CLIENT_NOT_CONNECTED = -9 # Bitcoin is not connected + CLIENT_IN_INITIAL_DOWNLOAD = -10 # Still downloading initial blocks + + # Wallet errors + WALLET_ERROR = -4 # Unspecified problem with wallet (key not found etc.) + WALLET_INSUFFICIENT_FUNDS = -6 # Not enough funds in wallet or account + WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name + WALLET_KEYPOOL_RAN_OUT = -12 # Keypool ran out, call keypoolrefill first + WALLET_UNLOCK_NEEDED = -13 # Enter the wallet passphrase with walletpassphrase first + WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect + WALLET_WRONG_ENC_STATE = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) + WALLET_ENCRYPTION_FAILED = -16 # Failed to encrypt the wallet + WALLET_ALREADY_UNLOCKED = -17 # Wallet is already unlocked + + def __init__(self, error): + Exception.__init__(self, error['message']) + self.code = error['code'] + + +##### General application defined errors +class SafeMode(BitcoinException): + """ + Operation denied in safe mode (run ``bitcoind`` with ``-disablesafemode``). + """ + + +class JSONTypeError(BitcoinException): + """ + Unexpected type was passed as parameter + """ +InvalidAmount = JSONTypeError # Backwards compatibility + + +class InvalidAddressOrKey(BitcoinException): + """ + Invalid address or key. + """ +InvalidTransactionID = InvalidAddressOrKey # Backwards compatibility + + +class OutOfMemory(BitcoinException): + """ + Out of memory during operation. + """ + + +class InvalidParameter(BitcoinException): + """ + Invalid parameter provided to RPC call. + """ + + +##### Client errors +class ClientException(BitcoinException): + """ + P2P network error. + This exception is never raised but functions as a superclass + for other P2P client exceptions. + """ + + +class NotConnected(ClientException): + """ + Not connected to any peers. + """ + + +class DownloadingBlocks(ClientException): + """ + Client is still downloading blocks. + """ + + +##### Wallet errors +class WalletError(BitcoinException): + """ + Unspecified problem with wallet (key not found etc.) + """ +SendError = WalletError # Backwards compatibility + +class InsufficientFunds(WalletError): + """ + Insufficient funds to complete transaction in wallet or account + """ + +class InvalidAccountName(WalletError): + """ + Invalid account name + """ + + +class KeypoolRanOut(WalletError): + """ + Keypool ran out, call keypoolrefill first + """ + + +class WalletUnlockNeeded(WalletError): + """ + Enter the wallet passphrase with walletpassphrase first + """ + + +class WalletPassphraseIncorrect(WalletError): + """ + The wallet passphrase entered was incorrect + """ + + +class WalletWrongEncState(WalletError): + """ + Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) + """ + + +class WalletEncryptionFailed(WalletError): + """ + Failed to encrypt the wallet + """ + + +class WalletAlreadyUnlocked(WalletError): + """ + Wallet is already unlocked + """ + + +# For convenience, we define more specific exception classes +# for the more common errors. +_exception_map = { + BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode, + BitcoinException.TYPE_ERROR: JSONTypeError, + BitcoinException.WALLET_ERROR: WalletError, + BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey, + BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, + BitcoinException.OUT_OF_MEMORY: OutOfMemory, + BitcoinException.INVALID_PARAMETER: InvalidParameter, + BitcoinException.CLIENT_NOT_CONNECTED: NotConnected, + BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks, + BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, + BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName, + BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut, + BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded, + BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect, + BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState, + BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed, + BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked, +} + + +def _wrap_exception(error): + """ + Convert a JSON error object to a more specific Bitcoin exception. + """ + return _exception_map.get(error['code'], BitcoinException)(error) diff --git a/mmgen/proxy.py b/mmgen/rpc/proxy.py similarity index 93% rename from mmgen/proxy.py rename to mmgen/rpc/proxy.py index 629e5802..16f3a548 100755 --- a/mmgen/proxy.py +++ b/mmgen/rpc/proxy.py @@ -1,4 +1,9 @@ """ + Copyright (C) 2013 by philemon + Added configurable http_timeout + + Previous copyright from bitcoin-python/proxy.py: + Copyright 2011 Jeff Garzik AuthServiceProxy has the following improvements over python-jsonrpc's @@ -47,9 +52,6 @@ except ImportError: USER_AGENT = "AuthServiceProxy/0.1" -HTTP_TIMEOUT = 7200 - - class JSONRPCException(Exception): def __init__(self, rpcError): Exception.__init__(self) @@ -57,7 +59,7 @@ class JSONRPCException(Exception): class AuthServiceProxy(object): - def __init__(self, serviceURL, serviceName=None): + def __init__(self, serviceURL, serviceName=None, http_timeout=30): self.__serviceURL = serviceURL self.__serviceName = serviceName self.__url = urlparse.urlparse(serviceURL) @@ -71,10 +73,10 @@ class AuthServiceProxy(object): self.__authhdr = "Basic ".encode('utf8') + base64.b64encode(authpair) if self.__url.scheme == 'https': self.__conn = httplib.HTTPSConnection(self.__url.hostname, port, None, None,False, - HTTP_TIMEOUT) + http_timeout) else: self.__conn = httplib.HTTPConnection(self.__url.hostname, port, False, - HTTP_TIMEOUT) + http_timeout) def __getattr__(self, name): if self.__serviceName != None: diff --git a/mmgen/rpc/util.py b/mmgen/rpc/util.py new file mode 100755 index 00000000..7165bc58 --- /dev/null +++ b/mmgen/rpc/util.py @@ -0,0 +1,49 @@ +# Copyright (c) 2010 Witchspace +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Generic utilities used by bitcoin client library.""" +from copy import copy + + +class DStruct(object): + """ + Simple dynamic structure, like :const:`collections.namedtuple` but more flexible + (and less memory-efficient) + """ + # Default arguments. Defaults are *shallow copied*, to allow defaults such as []. + _fields = [] + _defaults = {} + + def __init__(self, *args_t, **args_d): + # order + if len(args_t) > len(self._fields): + raise TypeError("Number of arguments is larger than of predefined fields") + # Copy default values + for (k, v) in self._defaults.iteritems(): + self.__dict__[k] = copy(v) + # Set pass by value arguments + self.__dict__.update(zip(self._fields, args_t)) + # dict + self.__dict__.update(args_d) + + def __repr__(self): + return '{module}.{classname}({slots})'.format( + module=self.__class__.__module__, classname=self.__class__.__name__, + slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in + self.__dict__.iteritems())) diff --git a/mmgen/tx.py b/mmgen/tx.py index ccea36c6..536abcdd 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -36,20 +36,16 @@ specified recipient address. } -def connect_to_bitcoind(mmgen=False): +def connect_to_bitcoind(http_timeout=30): host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword" cfg = get_cfg_options((user,passwd)) - if mmgen: - import mmgen.connection - f = mmgen.connection.MMGenBitcoinConnection - else: - import bitcoinrpc.connection - f = bitcoinrpc.connection.BitcoinConnection + import mmgen.rpc.connection + f = mmgen.rpc.connection.BitcoinConnection try: - c = f(cfg[user],cfg[passwd],host,port) + c = f(cfg[user],cfg[passwd],host,port,http_timeout=http_timeout) except: msg("Unable to establish RPC connection with bitcoind") sys.exit(2)