class BitcoindClient(BaseClient): """ Class to interact with bitcoind, the Bitcoin deamon """ @staticmethod def from_config(configfile=None, network='bitcoin'): """ Read settings from bitcoind config file :param configfile: Path to config file. Leave empty to look in default places :type: str :param network: Bitcoin mainnet or testnet. Default is bitcoin mainnet :type: str :return BitcoindClient: """ try: config = configparser.ConfigParser(strict=False) except TypeError: config = configparser.ConfigParser() config_fn = 'bitcoin.conf' if isinstance(network, Network): network = network.name if network == 'testnet': config_fn = 'bitcoin-testnet.conf' cfn = None if not configfile: config_locations = [ '~/.bitcoinlib', '~/.bitcoin', '~/Application Data/Bitcoin', '~/Library/Application Support/Bitcoin' ] for location in config_locations: cfn = Path(location, config_fn).expanduser() if cfn.exists(): break else: cfn = Path(BCL_DATA_DIR, 'config', configfile) if not cfn or not cfn.is_file(): raise ConfigError( "Config file %s not found. Please install bitcoin client and specify a path to config " "file if path is not default. Or place a config file in .bitcoinlib/bitcoin.conf to " "reference to an external server." % cfn) try: config.read(cfn) except Exception: with cfn.open() as f: config_string = '[rpc]\n' + f.read() config.read_string(config_string) testnet = _read_from_config(config, 'rpc', 'testnet') if testnet: network = 'testnet' if _read_from_config(config, 'rpc', 'rpcpassword') == 'specify_rpc_password': raise ConfigError("Please update config settings in %s" % cfn) if network == 'testnet': port = 18332 else: port = 8332 port = _read_from_config(config, 'rpc', 'rpcport', port) server = '127.0.0.1' server = _read_from_config(config, 'rpc', 'rpcconnect', server) server = _read_from_config(config, 'rpc', 'bind', server) server = _read_from_config(config, 'rpc', 'externalip', server) url = "http://%s:%s@%s:%s" % (config.get( 'rpc', 'rpcuser'), config.get('rpc', 'rpcpassword'), server, port) return BitcoindClient(network, url) def __init__(self, network='bitcoin', base_url='', denominator=100000000, *args): """ Open connection to bitcoin node :param network: Bitcoin mainnet or testnet. Default is bitcoin mainnet :type: str :param base_url: Connection URL in format http(s)://user:password@host:port. :type: str :param denominator: Denominator for this currency. Should be always 100000000 (satoshis) for bitcoin :type: str """ if isinstance(network, Network): network = network.name if not base_url: bdc = self.from_config('', network) base_url = bdc.base_url network = bdc.network if len(base_url.split(':')) != 4: raise ConfigError( "Bitcoind connection URL must be of format 'http(s)://user:password@host:port," "current format is %s. Please set url in providers.json file or check bitcoin config " "file" % base_url) if 'password' in base_url: raise ConfigError( "Invalid password in bitcoind provider settings. " "Please replace default password and set url in providers.json or bitcoin.conf file" ) _logger.info("Connect to bitcoind on %s" % base_url) self.proxy = AuthServiceProxy(base_url) super(self.__class__, self).__init__(network, PROVIDERNAME, base_url, denominator, *args) # def getbalance def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): txs = [] res = self.proxy.getaddressinfo(address) if not (res['ismine'] or res['iswatchonly']): raise ClientError( "Address %s not found in bitcoind wallet, use 'importpubkey' or 'importaddress' to add " "address to wallet." % address) txs_list = self.proxy.listunspent(0, 99999999, [address]) for t in sorted(txs_list, key=lambda x: x['confirmations'], reverse=True): txs.append({ 'address': t['address'], 'tx_hash': t['txid'], 'confirmations': t['confirmations'], 'output_n': t['vout'], 'input_n': -1, 'block_height': None, 'fee': None, 'size': 0, 'value': int(t['amount'] * self.units), 'script': t['scriptPubKey'], 'date': None, }) if t['txid'] == after_txid: txs = [] return txs def _parse_transaction(self, tx, block_height=None, get_input_values=True): t = Transaction.import_raw(tx['hex'], network=self.network) t.confirmations = None if 'confirmations' not in tx else tx[ 'confirmations'] if t.confirmations or block_height: t.status = 'confirmed' t.verified = True for i in t.inputs: if i.prev_hash == b'\x00' * 32: i.script_type = 'coinbase' continue if get_input_values: txi = self.proxy.getrawtransaction(to_hexstring(i.prev_hash), 1) i.value = int( round( float(txi['vout'][i.output_n_int]['value']) / self.network.denominator)) for o in t.outputs: o.spent = None t.block_hash = tx.get('block_hash', tx['txid']) # FIXME, use only one t.block_height = block_height t.version = struct.pack('>L', tx['version']) t.date = datetime.utcfromtimestamp(tx['time']) t.update_totals() return t def gettransaction(self, txid): tx = self.proxy.getrawtransaction(txid, 1) return self._parse_transaction(tx) # def gettransactions def getrawtransaction(self, txid): res = self.proxy.getrawtransaction(txid) return res def sendrawtransaction(self, rawtx): res = self.proxy.sendrawtransaction(rawtx) return {'txid': res, 'response_dict': res} def estimatefee(self, blocks): pres = '' try: pres = self.proxy.estimatesmartfee(blocks) res = pres['feerate'] except KeyError as e: _logger.info("bitcoind error: %s, %s" % (e, pres)) res = self.proxy.estimatefee(blocks) return int(res * self.units) def blockcount(self): return self.proxy.getblockcount() def mempool(self, txid=''): txids = self.proxy.getrawmempool() if not txid: return txids elif txid in txids: return [txid] return [] def getblock(self, blockid, parse_transactions=True, page=None, limit=None): if isinstance(blockid, int): blockid = self.proxy.getblockhash(blockid) if not limit: limit = 99999 txs = [] if parse_transactions: bd = self.proxy.getblock(blockid, 2) for tx in bd['tx'][(page - 1) * limit:page * limit]: # try: tx['time'] = bd['time'] tx['txid'] = bd['hash'] txs.append( self._parse_transaction(tx, block_height=bd['height'], get_input_values=False)) # except Exception as e: # _logger.error("Could not parse tx %s with error %s" % (tx['txid'], e)) # txs += [tx['hash'] for tx in bd['tx'][len(txs):]] else: bd = self.proxy.getblock(blockid, 1) txs = bd['tx'] block = { 'bits': bd['bits'], 'depth': bd['confirmations'], 'block_hash': bd['hash'], 'height': bd['height'], 'merkle_root': bd['merkleroot'], 'nonce': bd['nonce'], 'prev_block': None if 'previousblockhash' not in bd else bd['previousblockhash'], 'time': bd['time'], 'tx_count': bd['nTx'], 'txs': txs, 'version': bd['version'], 'page': page, 'pages': None, 'limit': limit } return block def getrawblock(self, blockid): if isinstance(blockid, int): blockid = self.proxy.getblockhash(blockid) return self.proxy.getblock(blockid, 0) def isspent(self, txid, index): res = self.proxy.gettxout(txid, index) if not res: return True return False
class LitecoindClient(BaseClient): """ Class to interact with litecoind, the Litecoin deamon """ @staticmethod def from_config(configfile=None, network='litecoin'): """ Read settings from litecoind config file :param configfile: Path to config file. Leave empty to look in default places :type: str :param network: Litecoin mainnet or testnet. Default is litecoin mainnet :type: str :return LitecoindClient: """ if PY3: config = configparser.ConfigParser(strict=False) else: config = configparser.ConfigParser() config_fn = 'litecoin.conf' if isinstance(network, Network): network = network.name if network == 'testnet': config_fn = 'litecoin-testnet.conf' if not configfile: cfn = os.path.join(os.path.expanduser("~"), '.bitcoinlib/config/%s' % config_fn) if not os.path.isfile(cfn): # Linux cfn = os.path.join(os.path.expanduser("~"), '.litecoin/%s' % config_fn) if not os.path.isfile(cfn): # Try Windows path cfn = os.path.join(os.path.expanduser("~"), 'Application Data/Litecoin/%s' % config_fn) if not os.path.isfile(cfn): # Try Mac path cfn = os.path.join( os.path.expanduser("~"), 'Library/Application Support/Litecoin/%s' % config_fn) if not os.path.isfile(cfn): raise ConfigError( "Please install litecoin client and specify a path to config file if path is not " "default. Or place a config file in .bitcoinlib/config/litecoin.conf to reference to " "an external server.") else: cfn = os.path.join(BCL_CONFIG_DIR, configfile) if not os.path.isfile(cfn): raise ConfigError("Config file %s not found" % cfn) try: config.read(cfn) except Exception: with open(cfn, 'r') as f: config_string = '[rpc]\n' + f.read() config.read_string(config_string) testnet = _read_from_config(config, 'rpc', 'testnet') if testnet: network = 'testnet' if _read_from_config(config, 'rpc', 'rpcpassword') == 'specify_rpc_password': raise ConfigError("Please update config settings in %s" % cfn) if network == 'testnet': port = 19332 else: port = 9332 port = _read_from_config(config, 'rpc', 'rpcport', port) server = '127.0.0.1' server = _read_from_config(config, 'rpc', 'rpcconnect', server) server = _read_from_config(config, 'rpc', 'bind', server) server = _read_from_config(config, 'rpc', 'externalip', server) url = "http://%s:%s@%s:%s" % (config.get( 'rpc', 'rpcuser'), config.get('rpc', 'rpcpassword'), server, port) return LitecoindClient(network, url) def __init__(self, network='litecoin', base_url='', denominator=100000000, *args): """ Open connection to litecoin node :param network: Litecoin mainnet or testnet. Default is litecoin mainnet :type: str :param base_url: Connection URL in format http(s)://user:password@host:port. :type: str :param denominator: Denominator for this currency. Should be always 100000000 (satoshis) for litecoin :type: str """ if isinstance(network, Network): network = network.name if not base_url: bdc = self.from_config('', network) base_url = bdc.base_url network = bdc.network if len(base_url.split(':')) != 4: raise ConfigError( "Litecoind connection URL must be of format 'http(s)://user:password@host:port," "current format is %s. Please set url in providers.json file or check litecoin config " "file" % base_url) if 'password' in base_url: raise ConfigError( "Invalid password in litecoind provider settings. " "Please replace default password and set url in providers.json or litecoin.conf file" ) _logger.info("Connect to litecoind on %s" % base_url) self.proxy = AuthServiceProxy(base_url) super(self.__class__, self).__init__(network, PROVIDERNAME, base_url, denominator, *args) # def getbalance def getutxos(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): txs = [] res = self.proxy.getaddressinfo(address) if not (res['ismine'] or res['iswatchonly']): raise ClientError( "Address %s not found in litecoind wallet, use 'importaddress' to add address to " "wallet." % address) for t in self.proxy.listunspent(0, 99999999, [address]): txs.append({ 'address': t['address'], 'tx_hash': t['txid'], 'confirmations': t['confirmations'], 'output_n': t['vout'], 'input_n': -1, 'block_height': None, 'fee': None, 'size': 0, 'value': int(t['amount'] * self.units), 'script': t['scriptPubKey'], 'date': None, }) return txs def gettransaction(self, txid): tx = self.proxy.getrawtransaction(txid, 1) t = Transaction.import_raw(tx['hex'], network=self.network) t.confirmations = tx['confirmations'] if t.confirmations: t.status = 'confirmed' t.verified = True for i in t.inputs: if i.prev_hash == b'\x00' * 32: i.value = t.output_total i.script_type = 'coinbase' continue txi = self.proxy.getrawtransaction(to_hexstring(i.prev_hash), 1) i.value = int( round( float(txi['vout'][i.output_n_int]['value']) / self.network.denominator)) for o in t.outputs: o.spent = None t.block_hash = tx['blockhash'] t.version = struct.pack('>L', tx['version']) t.date = datetime.fromtimestamp(tx['blocktime']) t.update_totals() t.hash = txid return t # def gettransactions def getrawtransaction(self, txid): res = self.proxy.getrawtransaction(txid) return res def sendrawtransaction(self, rawtx): res = self.proxy.sendrawtransaction(rawtx) return {'txid': res, 'response_dict': res} def estimatefee(self, blocks): pres = '' try: pres = self.proxy.estimatesmartfee(blocks) res = pres['feerate'] except KeyError as e: _logger.warning("litecoind error: %s, %s" % (e, pres)) res = self.proxy.estimatefee(blocks) return int(res * self.units) def blockcount(self): return self.proxy.getblockcount() def mempool(self, txid=''): txids = self.proxy.getrawmempool() if not txid: return txids elif txid in txids: return [txid] return []