Exemple #1
0
    def __init__(self,
                 network='dash',
                 base_url='',
                 denominator=100000000,
                 *args):
        """
        Open connection to dashcore node

        :param network: Dash mainnet or testnet. Default is dash 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 Dash
        :type: str
        """
        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(
                "Dashd connection URL must be of format 'http(s)://user:password@host:port,"
                "current format is %s. Please set url in providers.json file" %
                base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password 'password' in dashd provider settings. "
                "Please set password and url in providers.json file")
        _logger.info("Connect to dashd")
        self.proxy = AuthServiceProxy(base_url)
        super(self.__class__, self).__init__(network, PROVIDERNAME, base_url,
                                             denominator, *args)
Exemple #2
0
    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)
Exemple #3
0
 def __init__(self, network='bitcoin', base_url='', denominator=100000000, api_key=''):
     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" % base_url)
     if 'password' in base_url:
         raise ConfigError("Invalid password 'password' in bitcoind provider settings. "
                           "Please set password and url in providers.json 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, api_key)
Exemple #4
0
 def __init__(self, name, m, n, signers, psbt, address_index):
     # Name of the wallet
     self.name = name
     # Signers required for bitcoin tx
     self.m = m
     # Total signers
     self.n = n
     # Dictionary
     self.signers = signers
     # Hex string
     self.psbt = psbt
     # Depth in HD derivation
     self.address_index = address_index
     # RPC connection to corresponding watch-only wallet in Bitcoin Core
     wallet_uri = self.wallet_template.format(**settings["rpc"],
                                              name=self.watchonly_name())
     self.wallet_rpc = AuthServiceProxy(wallet_uri)
Exemple #5
0
class DashdClient(BaseClient):
    """
    Class to interact with dashd, the Dash deamon
    """
    @staticmethod
    def from_config(configfile=None, network='dash'):
        """
        Read settings from dashd config file

        :param configfile: Path to config file. Leave empty to look in default places
        :type: str
        :param network: Dash mainnet or testnet. Default is dash mainnet
        :type: str

        :return DashdClient:
        """
        config = configparser.ConfigParser(strict=False)
        if not configfile:
            cfn = os.path.join(os.path.expanduser("~"),
                               '.bitcoinlib/dash.conf')
            if not os.path.isfile(cfn):
                cfn = os.path.join(os.path.expanduser("~"),
                                   '.dashcore/dash.conf')
            if not os.path.isfile(cfn):
                raise ConfigError(
                    "Please install dash client and specify a path to config file if path is not "
                    "default. Or place a config file in .bitcoinlib/dash.conf to reference to "
                    "an external server.")
        else:
            cfn = os.path.join(BCL_DATA_DIR, 'config', configfile)
            if not os.path.isfile(cfn):
                raise ConfigError("Config file %s not found" % cfn)
        with open(cfn, 'r') as f:
            config_string = '[rpc]\n' + f.read()
        config.read_string(config_string)

        try:
            if int(config.get('rpc', 'testnet')):
                network = 'testnet'
        except configparser.NoOptionError:
            pass
        if config.get('rpc', 'rpcpassword') == 'specify_rpc_password':
            raise ConfigError("Please update config settings in %s" % cfn)
        try:
            port = config.get('rpc', 'port')
        except configparser.NoOptionError:
            if network == 'testnet':
                port = 19998
            else:
                port = 9998
        server = '127.0.0.1'
        if 'bind' in config['rpc']:
            server = config.get('rpc', 'bind')
        elif 'externalip' in config['rpc']:
            server = config.get('rpc', 'externalip')
        url = "http://%s:%s@%s:%s" % (config.get(
            'rpc', 'rpcuser'), config.get('rpc', 'rpcpassword'), server, port)
        return DashdClient(network, url)

    def __init__(self,
                 network='dash',
                 base_url='',
                 denominator=100000000,
                 *args):
        """
        Open connection to dashcore node

        :param network: Dash mainnet or testnet. Default is dash 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 Dash
        :type: str
        """
        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(
                "Dashd connection URL must be of format 'http(s)://user:password@host:port,"
                "current format is %s. Please set url in providers.json file" %
                base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password 'password' in dashd provider settings. "
                "Please set password and url in providers.json file")
        _logger.info("Connect to dashd")
        self.proxy = AuthServiceProxy(base_url)
        super(self.__class__, self).__init__(network, PROVIDERNAME, base_url,
                                             denominator, *args)

    def _parse_transaction(self, tx, block_height=None, get_input_values=True):
        t = Transaction.parse_hex(tx['hex'],
                                  strict=False,
                                  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_txid == b'\x00' * 32:
                i.script_type = 'coinbase'
                continue
            if get_input_values:
                txi = self.proxy.getrawtransaction(i.prev_txid.hex(), 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_height = block_height
        t.version = tx['version'].to_bytes(4, 'big')
        t.date = datetime.utcfromtimestamp(tx['blocktime'])
        t.update_totals()
        return t

    def gettransaction(self, txid):
        tx = self.proxy.getrawtransaction(txid, 1)
        return self._parse_transaction(tx)

    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):
        try:
            res = self.proxy.estimatesmartfee(blocks)['feerate']
        except KeyError:
            res = self.proxy.estimatefee(blocks)
        return int(res * self.units)

    def blockcount(self):
        return self.proxy.getblockcount()

    def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS):
        txs = []

        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'],
                'txid': 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 getblock(self, blockid, parse_transactions=True, page=1, 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['blocktime'] = bd['time']
                tx['blockhash'] = 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': int(bd['bits'], 16),
            'depth': bd['confirmations'],
            'hash': bd['hash'],
            'height': bd['height'],
            'merkle_root': bd['merkleroot'],
            'nonce': bd['nonce'],
            'prev_block': bd['previousblockhash'],
            'time': bd['time'],
            'total_txs': 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

    def getinfo(self):
        info = self.proxy.getmininginfo()
        return {
            'blockcount': info['blocks'],
            'chain': info['chain'],
            'difficulty': int(info['difficulty']),
            'hashrate': int(info['networkhashps']),
            'mempool_size': int(info['pooledtx']),
        }
Exemple #6
0
class DashdClient(BaseClient):
    """
    Class to interact with dashd, the Dash deamon
    """

    @staticmethod
    def from_config(configfile=None, network='dash'):
        """
        Read settings from dashd config file

        :param configfile: Path to config file. Leave empty to look in default places
        :type: str
        :param network: Dash mainnet or testnet. Default is dash mainnet
        :type: str

        :return DashdClient:
        """
        config = configparser.ConfigParser(strict=False)
        if not configfile:
            cfn = os.path.join(os.path.expanduser("~"), '.bitcoinlib/config/dash.conf')
            if not os.path.isfile(cfn):
                cfn = os.path.join(os.path.expanduser("~"), '.dashcore/dash.conf')
            if not os.path.isfile(cfn):
                raise ConfigError("Please install dash client and specify a path to config file if path is not "
                                  "default. Or place a config file in .bitcoinlib/config/dash.conf to reference to "
                                  "an external server.")
        else:
            cfn = os.path.join(BCL_DATA_DIR, 'config', configfile)
            if not os.path.isfile(cfn):
                raise ConfigError("Config file %s not found" % cfn)
        with open(cfn, 'r') as f:
            config_string = '[rpc]\n' + f.read()
        config.read_string(config_string)

        try:
            if int(config.get('rpc', 'testnet')):
                network = 'testnet'
        except configparser.NoOptionError:
            pass
        if config.get('rpc', 'rpcpassword') == 'specify_rpc_password':
            raise ConfigError("Please update config settings in %s" % cfn)
        try:
            port = config.get('rpc', 'port')
        except configparser.NoOptionError:
            if network == 'testnet':
                port = 19998
            else:
                port = 9998
        server = '127.0.0.1'
        if 'bind' in config['rpc']:
            server = config.get('rpc', 'bind')
        elif 'externalip' in config['rpc']:
            server = config.get('rpc', 'externalip')
        url = "http://%s:%s@%s:%s" % (config.get('rpc', 'rpcuser'), config.get('rpc', 'rpcpassword'), server, port)
        return DashdClient(network, url)

    def __init__(self, network='dash', base_url='', denominator=100000000, *args):
        """
        Open connection to dashcore node

        :param network: Dash mainnet or testnet. Default is dash 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 Dash
        :type: str
        """
        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("Dashd connection URL must be of format 'http(s)://user:password@host:port,"
                              "current format is %s. Please set url in providers.json file" % base_url)
        if 'password' in base_url:
            raise ConfigError("Invalid password 'password' in dashd provider settings. "
                              "Please set password and url in providers.json file")
        _logger.info("Connect to dashd on %s" % base_url)
        self.proxy = AuthServiceProxy(base_url)
        super(self.__class__, self).__init__(network, PROVIDERNAME, base_url, denominator, *args)

    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:
            txi = self.proxy.getrawtransaction(to_hexstring(i.prev_hash), 1)
            value = int(float(txi['vout'][i.output_n_int]['value']) / self.network.denominator)
            i.value = value
        t.block_hash = tx['blockhash']
        t.version = tx['version']
        t.date = datetime.fromtimestamp(tx['blocktime'])
        t.update_totals()
        return t

    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):
        try:
            res = self.proxy.estimatesmartfee(blocks)['feerate']
        except KeyError:
            res = self.proxy.estimatefee(blocks)
        return int(res * self.units)

    def blockcount(self):
        return self.proxy.getblockcount()

    def getutxos(self, address, after_txid='', max_txs=MAX_TRANSACTIONS):
        txs = []

        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
Exemple #7
0
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
Exemple #8
0
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 []
Exemple #9
0
class DogecoindClient(BaseClient):
    """
    Class to interact with dogecoind, the Dogecoin daemon
    """
    @staticmethod
    def from_config(configfile=None, network='dogecoin'):
        """
        Read settings from dogecoind config file

        :param configfile: Path to config file. Leave empty to look in default places
        :type: str
        :param network: Dogecoin mainnet or testnet. Default is dogecoin mainnet
        :type: str

        :return DogecoindClient:
        """
        try:
            config = configparser.ConfigParser(strict=False)
        except TypeError:
            config = configparser.ConfigParser()
        config_fn = 'dogecoin.conf'
        if isinstance(network, Network):
            network = network.name
        if network == 'testnet':
            config_fn = 'dogecoin-testnet.conf'

        cfn = None
        if not configfile:
            config_locations = [
                '~/.bitcoinlib', '~/.dogecoin', '~/Application Data/Dogecoin',
                '~/Library/Application Support/Dogecoin'
            ]
            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 dogecoin client and specify a path to config "
                "file if path is not default. Or place a config file in .bitcoinlib/dogecoin.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 = 44555
        else:
            port = 22555
        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 DogecoindClient(network, url)

    def __init__(self,
                 network='dogecoin',
                 base_url='',
                 denominator=100000000,
                 *args):
        """
        Open connection to dogecoin node

        :param network: Dogecoin mainnet or testnet. Default is dogecoin 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 dogecoin
        :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(
                "Dogecoind 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 dogecoin config "
                "file" % base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password in dogecoind provider settings. "
                "Please replace default password and set url in providers.json or dogecoin.conf file"
            )
        _logger.info("Connect to dogecoind")
        self.proxy = AuthServiceProxy(base_url)
        super(self.__class__, self).__init__(network, PROVIDERNAME, base_url,
                                             denominator, *args)

    def getutxos(self, address, after_txid='', max_txs=MAX_TRANSACTIONS):
        txs = []

        for t in self.proxy.listunspent(0, 99999999, [address]):
            txs.append({
                'address': t['address'],
                'txid': 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, block_height=None, get_input_values=True):
        tx = self.proxy.getrawtransaction(txid, 1)
        t = Transaction.parse_hex(tx['hex'],
                                  strict=False,
                                  network=self.network)
        t.confirmations = tx['confirmations']
        if t.confirmations:
            t.status = 'confirmed'
            t.verified = True
        for i in t.inputs:
            if i.prev_txid == b'\x00' * 32:
                i.value = t.output_total
                i.script_type = 'coinbase'
                continue
            if get_input_values:
                txi = self.proxy.getrawtransaction(i.prev_txid.hex(), 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.version = tx['version'].to_bytes(4, 'big')
        t.date = datetime.fromtimestamp(tx['blocktime'])
        t.block_height = block_height
        t.update_totals()
        return t

    def getrawtransaction(self, txid):
        return self.proxy.getrawtransaction(txid)

    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("dogecoind 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 getinfo(self):
        info = self.proxy.getmininginfo()
        return {
            'blockcount': info['blocks'],
            'chain': info['chain'],
            'difficulty': int(info['difficulty']),
            'hashrate': int(info['networkhashps']),
            'mempool_size': int(info['pooledtx']),
        }
Exemple #10
0
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:
        """
        config = configparser.ConfigParser(strict=False)
        if not configfile:
            cfn = os.path.join(os.path.expanduser("~"),
                               '.bitcoinlib/config/bitcoin.conf')
            if not os.path.isfile(cfn):
                cfn = os.path.join(os.path.expanduser("~"),
                                   '.bitcoin/bitcoin.conf')
            if not os.path.isfile(cfn):
                raise ConfigError(
                    "Please install bitcoin client and specify a path to config file if path is not "
                    "default. Or place a config file in .bitcoinlib/config/bitcoin.conf to reference to "
                    "an external server.")
        else:
            cfn = os.path.join(DEFAULT_SETTINGSDIR, configfile)
            if not os.path.isfile(cfn):
                raise ConfigError("Config file %s not found" % cfn)
        with open(cfn, 'r') as f:
            config_string = '[rpc]\n' + f.read()
        config.read_string(config_string)
        try:
            if int(config.get('rpc', 'testnet')):
                network = 'testnet'
        except configparser.NoOptionError:
            pass
        if config.get('rpc', 'rpcpassword') == 'specify_rpc_password':
            raise ConfigError("Please update config settings in %s" % cfn)
        try:
            port = config.get('rpc', 'port')
        except configparser.NoOptionError:
            if network == 'testnet':
                port = 18332
            else:
                port = 8332
        server = '127.0.0.1'
        if 'bind' in config['rpc']:
            server = config.get('rpc', 'bind')
        elif 'externalip' in config['rpc']:
            server = config.get('rpc', 'externalip')
        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 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" %
                base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password 'password' in bitcoind provider settings. "
                "Please set password and url in providers.json 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 getrawtransaction(self, txid):
        res = self.proxy.getrawtransaction(txid)
        return res

    def gettransaction(self, txid):
        tx = self.proxy.getrawtransaction(txid, 1)
        t = Transaction.import_raw(tx['hex'])
        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.hash = txid
        t.update_totals()
        return t

    def sendrawtransaction(self, rawtx):
        res = self.proxy.sendrawtransaction(rawtx)
        return {'txid': res, 'response_dict': res}

    def estimatefee(self, blocks):
        try:
            res = self.proxy.estimatesmartfee(blocks)['feerate']
        except KeyError:
            res = self.proxy.estimatefee(blocks)
        return int(res * self.units)
Exemple #11
0
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:
        """
        config = configparser.ConfigParser()
        if not configfile:
            cfn = os.path.join(os.path.expanduser("~"),
                               '.bitcoinlib/config/bitcoin.conf')
            if not os.path.isfile(cfn):
                cfn = os.path.join(os.path.expanduser("~"),
                                   '.bitcoin/bitcoin.conf')
            if not os.path.isfile(cfn):
                raise ConfigError(
                    "Please install bitcoin client and specify a path to config file if path is not "
                    "default. Or place a config file in .bitcoinlib/config/bitcoin.conf to reference to "
                    "an external server.")
        else:
            cfn = os.path.join(DEFAULT_SETTINGSDIR, configfile)
            if not os.path.isfile(cfn):
                raise ConfigError("Config file %s not found" % cfn)
        with open(cfn, 'r') as f:
            config_string = '[rpc]\n' + f.read()
        config.read_string(config_string)
        try:
            if config.get('rpc', 'testnet'):
                network = 'testnet'
        except configparser.NoOptionError:
            pass
        if config.get('rpc', 'rpcpassword') == 'specify_rpc_password':
            raise ConfigError("Please update config settings in %s" % cfn)
        try:
            port = config.get('rpc', 'port')
        except configparser.NoOptionError:
            if network == 'testnet':
                port = 18332
            else:
                port = 8332
        try:
            server = config.get('rpc', 'bind')
        except configparser.NoOptionError:
            server = '127.0.0.1'
        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,
                 api_key=''):
        """
        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
        :param api_key: Leave empty for
        :type: str
        """
        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" %
                base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password 'password' in bitcoind provider settings. "
                "Please set password and url in providers.json 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, api_key)

    def getrawtransaction(self, txid):
        res = self.proxy.getrawtransaction(txid)
        return res

    def sendrawtransaction(self, rawtx):
        return self.proxy.sendrawtransaction(rawtx)

    def estimatefee(self, blocks):
        try:
            res = self.proxy.estimatesmartfee(blocks)['feerate']
        except KeyError:
            res = self.proxy.estimatefee(blocks)
        return int(res * self.units)
Exemple #12
0
class BitcoindClient(BaseClient):
    @staticmethod
    def from_config(configfile=None, network='bitcoin'):
        config = configparser.ConfigParser()
        if not configfile:
            cfn = os.path.join(os.path.expanduser("~"),
                               '.bitcoin/bitcoin.conf')
            if not os.path.isfile(cfn):
                cfn = os.path.join(os.path.expanduser("~"),
                                   '.bitcoinlib/config/bitcoin.conf')
            if not os.path.isfile(cfn):
                raise ConfigError(
                    "Please install bitcoin client and specify a path to config file if path is not "
                    "default. Or place a config file in .bitcoinlib/config/bitcoin.conf to reference to "
                    "an external server.")
        else:
            cfn = os.path.join(DEFAULT_SETTINGSDIR, configfile)
            if not os.path.isfile(cfn):
                raise ConfigError("Config file %s not found" % cfn)
        with open(cfn, 'r') as f:
            config_string = '[rpc]\n' + f.read()
        config.read_string(config_string)
        if config.get('rpc', 'rpcpassword') == 'specify_rpc_password':
            raise ConfigError("Please update config settings in %s" % cfn)
        if network == 'bitcoin':
            port = 8332
        elif network == 'testnet':
            port = 18332
        else:
            raise ConfigError("Network %s not supported by BitcoindClient" %
                              network)
        try:
            server = config.get('rpc', 'server')
        except:
            server = '127.0.0.1'
        url = "http://%s:%s@%s:%s" % (config.get(
            'rpc', 'rpcuser'), config.get('rpc', 'rpcpassword'), server, port)
        return url

    def __init__(self,
                 network='bitcoin',
                 base_url='',
                 denominator=100000000,
                 api_key=''):
        if not base_url:
            base_url = self.from_config('', 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" %
                base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password 'password' in bitcoind provider settings. "
                "Please set password and url in providers.json 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, api_key)

    def getrawtransaction(self, txid):
        res = self.proxy.getrawtransaction(txid)
        return res

    def sendrawtransaction(self, rawtx):
        return self.proxy.sendrawtransaction(rawtx)

    def estimatefee(self, blocks):
        res = self.proxy.estimatefee(blocks)
        return int(res * self.units)

    def address_transactions(self, addresslist):
        # TODO: write this method if possible
        pass
Exemple #13
0
class MultiSig:

    wallet_template = "http://{username}:{password}@{host}:{port}/wallet/{name}"

    def __init__(self, name, m, n, signers, psbt, address_index):
        # Name of the wallet
        self.name = name
        # Signers required for bitcoin tx
        self.m = m
        # Total signers
        self.n = n
        # Dictionary
        self.signers = signers
        # Hex string
        self.psbt = psbt
        # Depth in HD derivation
        self.address_index = address_index
        # RPC connection to corresponding watch-only wallet in Bitcoin Core
        wallet_uri = self.wallet_template.format(**settings["rpc"],
                                                 name=self.watchonly_name())
        self.wallet_rpc = AuthServiceProxy(wallet_uri)

    def ready(self):
        return len(self.signers) == self.n

    def filename(self):
        return f"{self.name}.wallet"

    @classmethod
    def create(cls, name, m, n):
        # sanity check
        if m > n:
            raise JunctionError(
                f"\"m\" ({m}) must be no larger than \"n\" ({n})")

        # MultiSig instance
        multisig = cls(name, m, n, [], None, 0)

        # Never overwrite existing wallet files
        filename = multisig.filename()
        if os.path.exists(filename):
            raise JunctionError(f"{filename} already exists")

        # create a watch-only Bitcoin Core wallet
        multisig.create_watchonly()

        # Save a copy of wallet to disk
        multisig.save()

        # Return instance
        return multisig

    @classmethod
    def open(cls, filename):
        with open(filename) as f:
            multisig = cls.from_dict(json.load(f))
            logger.info(f"Opened wallet from {filename}")
            return multisig

    def save(self):
        filename = self.filename()
        # if this line broke inside json.dump, the json file would be emptyed :eek:
        data = self.to_dict()
        with open(filename, "w") as f:
            json.dump(data, f, indent=4)
            logger.info(f"Saved wallet to {filename}")

    @classmethod
    def from_dict(cls, d):
        if d["psbt"]:
            psbt = hwilib.serializations.PSBT()
            psbt.deserialize(d["psbt"])
            d["psbt"] = psbt
        return cls(**d)

    def to_dict(self):
        return {
            "name": self.name,
            "m": self.m,
            "n": self.n,
            "signers": self.signers,
            "psbt": self.psbt.serialize() if self.psbt else "",
            "address_index": self.address_index,
        }

    def add_signer(self, name, fingerprint, xpub, deriv_path):
        # FIXME: deriv_path not used ...
        if self.ready():
            raise JunctionError(
                f'Already have {len(self.signers)} of {self.n} required signers'
            )

        # Check if name used before
        if name in [signer["name"] for signer in self.signers]:
            raise JunctionError(f'Name "{name}" already taken')

        # check if fingerprint used before
        if fingerprint in [signer["fingerprint"] for signer in self.signers]:
            raise JunctionError(f'Fingerprint "{fingerprint}" already used')

        self.signers.append({
            "name": name,
            "fingerprint": fingerprint,
            "xpub": xpub
        })
        logger.info(f"Registered signer \"{name}\"")

        # Export next chunk watch-only addresses to Bitcoin Core if we're done adding signers
        if self.ready():
            self.export_watchonly()

        self.save()

    def remove_signer(signer_name):
        raise NotImplementedError()

    def descriptor(self):
        '''Descriptor for shared multisig addresses'''
        # TODO: consider using HWI's Descriptor class
        origin_path = "/44h/1h/0h"
        path_suffix = "/0/*"
        xpubs = [
            f'[{signer["fingerprint"]}{origin_path}]{signer["xpub"]}{path_suffix}'
            for signer in self.signers
        ]
        xpubs = ",".join(xpubs)
        descriptor = f"wsh(multi({self.m},{xpubs}))"
        logger.info(f"Exporting descriptor: {descriptor}")
        # validates and appends checksum
        r = self.wallet_rpc.getdescriptorinfo(descriptor)
        return r['descriptor']

    def address(self):
        # TODO: this check could work nicely as a decorator
        if not self.ready():
            raise JunctionError(
                f'{self.n} signers required, {len(self.signers)} registered')
        address = self.wallet_rpc.deriveaddresses(
            self.descriptor(), [self.address_index, self.address_index + 1])[0]
        self.address_index += 1
        self.save()
        return address

    def create_watchonly(self):
        # Create watch-only Bitcoin Core wallet (FIXME super ugly)
        # Prefix the wallet name with "junction_" to make it clear this is a junction wallet
        watch_only_name = self.watchonly_name()
        bitcoin_wallets = bitcoin_rpc.listwallets()
        if watch_only_name not in bitcoin_wallets:
            try:
                bitcoin_rpc.loadwallet(watch_only_name)
                logger.info(
                    f"Loaded watch-only Bitcoin Core wallet \"{watch_only_name}\""
                )
            except JSONRPCException as e:
                try:
                    bitcoin_rpc.createwallet(watch_only_name, True)
                    logger.info(
                        f"Created watch-only Bitcoin Core wallet \"{watch_only_name}\""
                    )
                except JSONRPCException as e:
                    raise JunctionError(
                        "Couldn't establish watch-only Bitcoin Core wallet")

    def watchonly_name(self):
        # maybe add a "junction_" prefix or something?
        return self.name

    def export_watchonly(self):
        logger.info("Starting watch-only export")
        self.wallet_rpc.importmulti([{
            "desc":
            self.descriptor(),
            "timestamp":
            "now",
            "range": [self.address_index, settings["wallet"]["address_chunk"]],
            "watchonly":
            True,
            "keypool":
            True,
            "internal":
            False,
        }])
        logger.info("Finished watch-only export")

    def create_psbt(self, recipient, amount):
        if self.psbt:
            raise JunctionError('PSBT already present')
        # FIXME bitcoin core can't generate change addrs
        change_address = self.address()
        raw_psbt = self.wallet_rpc.walletcreatefundedpsbt(
            # let Bitcoin Core choose inputs
            [],
            # Outputs
            [{
                recipient: amount
            }],
            # Locktime
            0,
            {
                # Include watch-only outputs
                "includeWatching": True,
                # Provide change address b/c Core can't generate it
                "changeAddress": change_address,
            },
            # Include BIP32 derivation paths in the PSBT
            True,
        )['psbt']
        # Serialize and save
        self.psbt = hwilib.serializations.PSBT()
        self.psbt.deserialize(raw_psbt)
        self.save()

    def remove_psbt(self):
        self.psbt = None

    def decode_psbt(self):
        return self.wallet_rpc.decodepsbt(self.psbt.serialize())

    def broadcast(self):
        psbt_hex = self.psbt.serialize()
        tx_hex = self.wallet_rpc.finalizepsbt(psbt_hex)["hex"]
        return self.wallet_rpc.sendrawtransaction(tx_hex)
Exemple #14
0
import json
import logging
import os.path
import hwilib
import toml

from bitcoinlib.services.authproxy import AuthServiceProxy, JSONRPCException
from pprint import pprint
from hwilib.serializations import PSBT

logger = logging.getLogger(__name__)
settings = toml.load("settings.toml")

bitcoin_uri = "http://{username}:{password}@{host}:{port}"
bitcoin_rpc = AuthServiceProxy(bitcoin_uri.format(**settings["rpc"]))


class JunctionError(Exception):
    pass


class MultiSig:

    wallet_template = "http://{username}:{password}@{host}:{port}/wallet/{name}"

    def __init__(self, name, m, n, signers, psbt, address_index):
        # Name of the wallet
        self.name = name
        # Signers required for bitcoin tx
        self.m = m
        # Total signers
Exemple #15
0
class QtumdClient(BaseClient):
    """
    Class to interact with qtumd, the Qtum deamon
    """
    @staticmethod
    def from_config(configfile=None, network='qtum'):
        """
        Read settings from qtumd config file

        :param configfile: Path to config file. Leave empty to look in default places
        :type: str
        :param network: Qtum mainnet or testnet. Default is qtum mainnet
        :type: str

        :return QtumdClient:
        """
        config = configparser.ConfigParser()
        if not configfile:
            cfn = os.path.join(os.path.expanduser("~"),
                               '.bitcoinlib/config/qtum.conf')
            if not os.path.isfile(cfn):
                cfn = os.path.join(os.path.expanduser("~"), '.qtum/qtum.conf')
            if not os.path.isfile(cfn):
                raise ConfigError(
                    "Please install qtum client and specify a path to config file if path is not "
                    "default. Or place a config file in .bitcoinlib/config/qtum.conf to reference to "
                    "an external server.")
        else:
            cfn = os.path.join(DEFAULT_SETTINGSDIR, configfile)
            if not os.path.isfile(cfn):
                raise ConfigError("Config file %s not found" % cfn)
        with open(cfn, 'r') as f:
            config_string = '[rpc]\n' + f.read()
        config.read_string(config_string)
        try:
            if config.get('rpc', 'testnet'):
                network = 'qtum_testnet'
        except configparser.NoOptionError:
            pass
        if config.get('rpc', 'rpcpassword') == 'specify_rpc_password':
            raise ConfigError("Please update config settings in %s" % cfn)
        try:
            port = config.get('rpc', 'port')
        except configparser.NoOptionError:
            if network == 'qtum_testnet':
                port = 8333
            else:
                port = 8333
        try:
            server = config.get('rpc', 'bind')
        except configparser.NoOptionError:
            server = '127.0.0.1'
        url = "http://%s:%s@%s:%s" % (config.get(
            'rpc', 'rpcuser'), config.get('rpc', 'rpcpassword'), server, port)
        return QtumdClient(network, url)

    def __init__(self,
                 network='qtum',
                 base_url='',
                 denominator=100000000,
                 api_key=''):
        """
        Open connection to qtum node

        :param network: Qtum mainnet or testnet. Default is qtum 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 qtum
        :type: str
        :param api_key: Leave empty for
        :type: str
        """
        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(
                "Qtumd connection URL must be of format 'http(s)://user:password@host:port,"
                "current format is %s. Please set url in providers.json file" %
                base_url)
        if 'password' in base_url:
            raise ConfigError(
                "Invalid password 'password' in qtumd provider settings. "
                "Please set password and url in providers.json file")
        _logger.info("Connect to qtumd on %s" % base_url)
        self.proxy = AuthServiceProxy(base_url)
        super(self.__class__, self).__init__(network, PROVIDERNAME, base_url,
                                             denominator, api_key)

    def getutxos(self, addresslist):
        res = self.proxy.listunspent(3, 9999999, addresslist)
        txs = []
        for tx in res:
            txs.append({
                'address': tx['address'],
                'tx_hash': tx['txid'],
                'confirmations': tx['confirmations'],
                'output_n': tx['vout'],
                'index': 0,
                'value': int(round(tx['amount'] * self.units, 0)),
                'script': tx['scriptPubKey'],
                'date': 0
            })
        return txs

    def getbalance(self, addresslist):
        res = self.proxy.listunspent(3, 9999999, addresslist)
        balance = 0
        for tx in res:
            balance += tx['values']
        return balance

    def getrawtransaction(self, txid):
        res = self.proxy.getrawtransaction(txid)
        return res

    def sendrawtransaction(self, rawtx):
        return self.proxy.sendrawtransaction(rawtx)

    def estimatefee(self, blocks):
        try:
            res = self.proxy.estimatesmartfee(blocks)['feerate']
        except KeyError:
            res = self.proxy.estimatefee(blocks)
        return int(res * self.units)