def session(conf=None, config_path=CONFIG_PATH, server_host=None, server_port=None, storage_drivers=None, metadata_dir=None, spv_headers_path=None, set_global=False): """ Create a blockstack session: * validate the configuration * load all storage drivers * initialize all storage drivers * load an API proxy to blockstack conf's fields override specific keyword arguments. Returns the API proxy object. """ if conf is None and config_path is not None: conf = get_config(config_path) if conf is not None: if server_host is None: server_host = conf['server'] if server_port is None: server_port = conf['port'] if storage_drivers is None: storage_drivers = conf['storage_drivers'] if metadata_dir is None: metadata_dir = conf['metadata'] if spv_headers_path is None: spv_headers_path = conf['blockchain_headers'] if storage_drivers is None: log.error("No storage driver(s) defined in the config file. Please set 'storage=' to a comma-separated list of drivers") sys.exit(1) # create proxy log.debug('Connect to {}:{}'.format(server_host, server_port)) proxy = BlockstackRPCClient(server_host, server_port) # load all storage drivers for storage_driver in storage_drivers.split(","): storage_impl = load_storage(storage_driver) if storage_impl is None: log.error("Failed to load storage driver '%s'" % (storage_driver)) sys.exit(1) rc = register_storage(storage_impl, conf) if not rc: log.error("Failed to initialize storage driver '%s' (%s)" % (storage_driver, rc)) sys.exit(1) # initialize SPV SPVClient.init(spv_headers_path) proxy.spv_headers_path = spv_headers_path proxy.conf = conf if set_global: set_default_proxy( proxy ) return proxy
def session(conf=None, config_path=CONFIG_PATH, server_host=None, server_port=None, wallet_password=None, storage_drivers=None, metadata_dir=None, spv_headers_path=None, set_global=False): """ Create a blockstack session: * validate the configuration * load all storage drivers * initialize all storage drivers * load an API proxy to blockstack conf's fields override specific keyword arguments. Returns the API proxy object. """ if set_global: if server_host is not None: os.environ['BLOCKSTACK_CLI_SERVER_HOST'] = server_host if server_port is not None: os.environ['BLOCKSTACK_CLI_SERVER_PORT'] = str(server_port) if conf is None: conf = get_config(config_path) if conf is None: log.error("Failed to read configuration file {}".format(config_path)) return None conf_version = conf.get('client_version', '') if not semver_match(conf_version, VERSION): log.error("Failed to use legacy configuration file {}".format(config_path)) return None if conf is not None: if server_host is None: server_host = conf['server'] if server_port is None: server_port = conf['port'] if storage_drivers is None: storage_drivers = conf['storage_drivers'] if metadata_dir is None: metadata_dir = conf['metadata'] if spv_headers_path is None: spv_headers_path = conf['bitcoind_spv_path'] if storage_drivers is None: msg = ('No storage driver(s) defined in the config file. ' 'Please set "storage=" to a comma-separated list of drivers') log.error(msg) sys.exit(1) # create proxy log.debug('Connect to {}:{}'.format(server_host, server_port)) proxy = BlockstackRPCClient(server_host, server_port) # load all storage drivers loaded = [] for storage_driver in storage_drivers.split(','): storage_impl = load_storage(storage_driver) if storage_impl is None: log.error('Failed to load storage driver "{}"'.format(storage_driver)) sys.exit(1) loaded.append(storage_driver) rc = register_storage(storage_impl, conf) if not rc: log.error('Failed to initialize storage driver "{}" ({})'.format(storage_driver, rc)) sys.exit(1) log.debug('Loaded storage drivers {}'.format(loaded)) # initialize SPV SPVClient.init(spv_headers_path) proxy.spv_headers_path = spv_headers_path proxy.conf = conf if set_global: set_default_proxy(proxy) return proxy
def serial_number_to_tx(serial_number, bitcoind_proxy, proxy=None): """ Convert a serial number into its transaction in the blockchain. Use an untrusted bitcoind connection to get the list of transactions, and use trusted SPV headers to ensure that the transaction obtained is on the main chain. @bitcoind_proxy must be a BitcoindConnection (from virtualchain.lib.session) Return the SPV-verified transaction object (as a dict) on success Return None on error """ proxy = get_default_proxy() if proxy is None else proxy parts = serial_number.split('-') block_id, tx_index = int(parts[0]), int(parts[1]) timeout = 1.0 while True: try: block_hash = bitcoind_proxy.getblockhash(block_id) block_data = bitcoind_proxy.getblock(block_hash) break except Exception as e: log.error('Unable to obtain block data; retrying...') time.sleep(timeout) timeout = timeout * 2 + random.random() * timeout rc = SPVClient.sync_header_chain(proxy.spv_headers_path, bitcoind_proxy.opts['bitcoind_server'], block_id) if not rc: msg = 'Failed to synchronize SPV header chain up to {}' log.error(msg.format(block_id)) return None # verify block header rc = SPVClient.block_header_verify(proxy.spv_headers_path, block_id, block_hash, block_data) if not rc: msg = 'Failed to verify block header for {} against SPV headers' log.error(msg.format(block_id)) return None # verify block txs rc = SPVClient.block_verify(block_data, block_data['tx']) if not rc: msg = 'Failed to verify block transaction IDs for {} against SPV headers' log.error(msg.format(block_id)) return None # sanity check if tx_index >= len(block_data['tx']): msg = 'Serial number {} references non-existant transaction {} (out of {} txs)' log.error(msg.format(serial_number, tx_index, len(block_data['tx']))) return None # obtain transaction txid = block_data['tx'][tx_index] tx = bitcoind_proxy.getrawtransaction(txid, 1) # verify tx rc = SPVClient.tx_verify(block_data['tx'], tx) if not rc: msg = 'Failed to verify block transaction {} against SPV headers' log.error(msg.format(txid)) return None # verify tx index if tx_index != SPVClient.tx_index(block_data['tx'], tx): msg = ('TX index mismatch: serial number identifies ' 'transaction number {} ({}), but got transaction {}') log.error( msg.format( tx_index, block_data['tx'][tx_index], block_data['tx'][SPVClient.tx_index(block_data['tx'], tx)])) return None # success! return tx
def txid_to_block_data(txid, bitcoind_proxy, proxy=None): """ Given a txid, get its block's data. Use SPV to verify the information we receive from the (untrusted) bitcoind host. @bitcoind_proxy must be a BitcoindConnection (from virtualchain.lib.session) Return the (block hash, block data, txdata) on success Return (None, None, None) on error """ proxy = get_default_proxy() if proxy is None else proxy timeout = 1.0 while True: try: untrusted_tx_data = bitcoind_proxy.getrawtransaction(txid, 1) untrusted_block_hash = untrusted_tx_data['blockhash'] untrusted_block_data = bitcoind_proxy.getblock( untrusted_block_hash) break except (OSError, IOError) as ie: log.exception(ie) log.error('Network error; retrying...') timeout = timeout * 2 + random.randint(0, timeout) continue except Exception as e: log.exception(e) return None, None, None # first, can we trust this block? is it in the SPV headers? untrusted_block_header_hex = virtualchain.block_header_to_hex( untrusted_block_data, untrusted_block_data['previousblockhash']) block_id = SPVClient.block_header_index( proxy.spv_headers_path, ('{}00'.format(untrusted_block_header_hex)).decode('hex')) if block_id < 0: # bad header log.error('Block header "{}" is not in the SPV headers ({})'.format( untrusted_block_header_hex, proxy.spv_headers_path)) return None, None, None # block header is trusted. Is the transaction data consistent with it? verified_block_header = virtualchain.block_verify(untrusted_block_data) if not verified_block_header: msg = ('Block transaction IDs are not consistent ' 'with the Merkle root of the trusted header') log.error(msg) return None, None, None # verify block hash verified_block_hash = virtualchain.block_header_verify( untrusted_block_data, untrusted_block_data['previousblockhash'], untrusted_block_hash) if not verified_block_hash: log.error('Block hash is not consistent with block header') return None, None, None # we trust the block hash, block data, and txids block_hash = untrusted_block_hash block_data = untrusted_block_data tx_data = untrusted_tx_data return block_hash, block_data, tx_data
break except (OSError, IOError), ie: log.exception(ie) log.error("Network error; retrying...") timeout = timeout * 2 + random.randint(0, timeout) continue except Exception, e: log.exception(e) return (None, None, None) # first, can we trust this block? is it in the SPV headers? untrusted_block_header_hex = virtualchain.block_header_to_hex( untrusted_block_data, untrusted_block_data['previousblockhash']) block_id = SPVClient.block_header_index(proxy.spv_headers_path, (untrusted_block_header_hex + "00").decode('hex')) if block_id < 0: # bad header log.error("Block header '%s' is not in the SPV headers (%s)" % (untrusted_block_header_hex, proxy.spv_headers_path)) return (None, None, None) # block header is trusted. Is the transaction data consistent with it? if not virtualchain.block_verify(untrusted_block_data): log.error( "Block transaction IDs are not consistent with the trusted header's Merkle root" ) return (None, None, None) # verify block hash