# -*- coding: utf-8 -*- """ Blockstack-client ~~~~~ copyright: (c) 2014-2015 by Halfmoon Labs, Inc. copyright: (c) 2016 by Blockstack.org This file is part of Blockstack-client. Blockstack-client is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Blockstack-client is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Blockstack-client. If not, see . """ import os import sys import threading from ..constants import * from ..keys import * from ..proxy import * from ..config import get_logger, get_utxo_provider_client from .blockchain import ( get_balance, is_address_usable, get_utxos, can_receive_name, get_tx_confirmations, get_tx_fee ) from ..scripts import UTXOException, is_name_valid log = get_logger('safety') def check_valid_name(fqu): """ Verify that a name is valid. Return None on success Return an error string on error """ rc = is_name_valid(fqu) if rc: return None # get a coherent reason why if '.' not in fqu: msg = ( 'The name specified is invalid. ' 'Names must end with a period followed by a valid TLD.' ) return msg name = fqu.split('.')[0] if not name: msg = ( 'The name specified is invalid. ' 'Names must be at least one character long, not including the TLD.' ) return msg if not is_b40(name): msg = ( 'The name specified is invalid. ' 'Names may only contain alphanumeric characters, ' 'dashes, and underscores.' ) return msg return 'The name is invalid' class ScatterGatherThread(threading.Thread): """ Scatter/gatter thread worker Useful for doing long-running queries in parallel """ def __init__(self, rpc_call): threading.Thread.__init__(self) self.rpc_call = rpc_call self.result = None self.has_result = False self.result_mux = threading.Lock() self.result_mux.acquire() def get_result(self): """ Wait for data and get it """ self.result_mux.acquire() res = self.result self.result_mux.release() return res def post_result(self, res): """ Give back result and release """ if self.has_result: return self.has_result = True self.result = res self.result_mux.release() return @classmethod def do_work(cls, rpc_call): """ Run the given RPC call and post the result """ try: log.debug("Run task {}".format(rpc_call)) res = rpc_call() log.debug("Task exit {}".format(rpc_call)) return res except Exception as e: log.exception(e) log.debug("Task exit {}".format(rpc_call)) return {'error': 'Task encountered a fatal exception:\n{}'.format(traceback.format_exc())} def run(self): res = ScatterGatherThread.do_work(self.rpc_call) self.post_result(res) class ScatterGather(object): """ Scatter/gather work pool Give it a few tasks, and it will run them in parallel """ def __init__(self): self.tasks = {} self.ran = False self.results = {} def add_task(self, result_name, rpc_call): assert result_name not in self.tasks.keys(), "Duplicate task: {}".format(result_name) self.tasks[result_name] = rpc_call def get_result(self, result_name): assert self.ran assert result_name in self.results, "Missing task: {}".format(result_name) return self.results[result_name] def get_results(self): """ Get the set of results """ assert self.ran return self.results def run_tasks(self, single_thread=True): """ Run all queued tasks, wait for them all to finish, and return the set of results """ if not single_thread: threads = {} for task_name, task_call in self.tasks.items(): log.debug("Start task '{}'".format(task_name)) thr = ScatterGatherThread(task_call) thr.start() threads[task_name] = thr for task_name, thr in threads.items(): log.debug("Join task '{}'".format(task_name)) thr.join() res = thr.get_result() self.results[task_name] = res else: # for testing purposes for task_name, task_call in self.tasks.items(): res = ScatterGatherThread.do_work(task_call) self.results[task_name] = res self.ran = True return self.results def operation_sanity_checks(fqu, operations, scatter_gather, payment_privkey_info, owner_privkey_info, required_checks=[], min_confirmations=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, transfer_address=None, proxy=None): """ Do a sanity check on carrying out a sequence of operations on a given name. Prime the given scatter/gather context with the set of necessary callbacks. Any update, transfer, renew, or revoke operation should pass these tests: * the payment address must have enough BTC for the transaction and dust fees * the owner address can't have any pending transactions * the payment address can't have any pending transactions If transferring: * the transfer address must be suitable for receiving the name If preordering or renewing: * the payment address must have enough BTC for the name cost If preordering: * the name must not be registered If not preordering: * name must be registered * name must be owned by the owner address Return {'status': True} on success Return {'error': ...} on error """ config_dir = os.path.dirname(config_path) wallet_path = os.path.join(config_dir, WALLET_FILENAME) if proxy is None: proxy = get_default_proxy(config_path) owner_address = get_privkey_info_address(owner_privkey_info) payment_address = get_privkey_info_address(payment_privkey_info) if transfer_address: transfer_address = str(transfer_address) sg = scatter_gather def _is_name_available(): """ is name available? (scatter/gather worker) """ if is_name_registered(fqu, proxy=proxy): return {'error': '{} is already registered.'.format(fqu)} else: return {'status': True} def _is_name_registered(): """ is name taken already? (scatter/gather worker) """ if not is_name_registered(fqu, proxy=proxy): return {'error': '{} is not registered.'.format(fqu)} else: return {'status': True} def _can_receive_name(addr): """ can owner address receive name? (scatter/gather worker) """ if not can_receive_name(addr, proxy=proxy): msg = 'Address {} owns too many names already.'.format(addr) return {'error': msg} else: return {'status': True} def _is_address_usable(addr): """ does the given address have unconfirmed transactions? (scatter/gather worker) """ if not is_address_usable(addr, config_path=config_path, min_confirmations=min_confirmations): msg = ( 'Address {} has insufficiently confirmed transactions. ' 'Wait and try later.' ) msg = msg.format(addr) return {'error': msg} else: return {'status': True} def _is_name_owner(addr): """ Is the given address the name owner? """ res = get_names_owned_by_address(addr, proxy=proxy) if 'error' in res: return {'error': res['error']} else: return {'status': fqu in res} check_names = { 'is_owner_address_usable': lambda: _is_address_usable(owner_address), 'is_payment_address_usable': lambda: _is_address_usable(payment_address), 'is_name_available': _is_name_available, 'owner_can_receive': lambda: _can_receive_name(owner_address), 'is_name_registered': _is_name_registered, 'is_name_owner': lambda: _is_name_owner(owner_address), 'recipient_can_receive': lambda: _can_receive_name(transfer_address), } # common to all operations required = ['is_payment_address_usable'] for req in required: if req not in required_checks: required_checks.append(req) # sanity check for req in required_checks: assert req in check_names.keys(), "Invalid check '{}'".format(req) # queue commands for req in required_checks: sg.add_task( req, check_names[req] ) # add tasks for fees res = get_operation_fees(fqu, operations, sg, payment_privkey_info, owner_privkey_info, payment_address=payment_address, owner_address=owner_address, transfer_address=transfer_address, min_payment_confs=min_confirmations, config_path=config_path, proxy=proxy ) if 'error' in res: log.error("Failed to get operation fees: {}".format(res['error'])) return {'error': 'Failed to calculate transaction fees'} log.debug("Queued tasks for {} on {}: {}".format(', '.join(operations), fqu, ', '.join(sorted(sg.tasks.keys())))) # scatter/gather primed! return {'status': True} def interpret_operation_sanity_checks( name, operations, scatter_gather ): """ Interpret the set of scatter/gather task results. """ reply = {} sg = scatter_gather # scan for errors errors = [] all_results = sg.get_results() for res_key in all_results.keys(): res = all_results[res_key] if 'error' in res: log.error("Task '{}' failed: {}".format(res_key, all_results[res_key]['error'])) errors.append(all_results[res_key]['error']) else: reply[res_key] = all_results[res_key]['status'] if 'get_balance' in reply: fees_reply = interpret_operation_fees( operations, sg, balance=reply['get_balance'] ) if 'error' in fees_reply: errors.append(fees_reply['error']) del fees_reply['error'] reply.update(fees_reply) if len(errors) > 0: reply['error'] = 'Operation sanity checks failed:\n\n{}'.format( '\n'.join([' * ' + err for err in errors] )) return reply def get_operation_fees(name, operations, scatter_gather, payment_privkey_info, owner_privkey_info, proxy=None, config_path=CONFIG_PATH, payment_address=None, min_payment_confs=TX_MIN_CONFIRMATIONS, owner_address=None, transfer_address=None): """ Given a list of operations and a scatter/gather context, go prime it to fetch the cost of each operation. Operations must be a list containing 'preorder', 'register', 'update', 'transfer', 'revoke', or 'renewal' The scatter/gather context, when executed, will yield the following results: All operations will have: * "tx_fee": the transaction fee (including dust) * "insufficient": whether or not we had sufficient funds to calculate the tx_fee * "estimate": whether or not this is a rough estimate (i.e. if we don't have the payment info on hand) Preorder and Renewal will also have: * "name_cost": the cost of the name itself Task results will be named after their operations. Return {'status': True} on success Return {'error': ...} on failure Raise on invalid argument """ from .nameops import ( estimate_preorder_tx_fee, estimate_register_tx_fee, estimate_update_tx_fee, estimate_transfer_tx_fee, estimate_renewal_tx_fee, estimate_revoke_tx_fee ) if payment_privkey_info is not None: payment_address = get_privkey_info_address(payment_privkey_info) if owner_privkey_info is not None: owner_address = get_privkey_info_address(owner_privkey_info) # fee estimation: cost of name + cost of preorder transaction + # cost of registration transaction + cost of update transaction + cost of transfer transaction reply = {} if owner_address: owner_address = str(owner_address) if payment_address: payment_address = str(payment_address) if transfer_address: transfer_address = str(transfer_address) assert owner_address, "Owner address or owner_privkey_info required" assert payment_address, "Payment address or payment_privkey_info required" if 'transfer' in operations: assert transfer_address, "Transfer address required" log.debug("Get total operation fees for running '{}' on {} owned by {} paid by {}".format(','.join(operations), name, owner_address, payment_address)) def _get_balance(): """ get payment address balance (scatter/gather worker) """ balance = get_balance(payment_address, config_path=config_path) if balance is None: msg = 'Failed to get balance' return {'error': msg} else: return {'status': balance} def _estimate_preorder_tx(): """ Estimate preorder tx cost Return {'status': True, 'name_cost': ..., 'tx_fee': ..., 'insufficient': ...} on success Return {'error': ...} on failure """ name_cost = None try: res = get_name_cost(name, proxy=proxy) if 'error' in res: return {'error': 'Failed to get name cost'} name_cost = res['satoshis'] except Exception as e: log.exception(e) return {'error': 'Could not get name price'} try: owner_privkey_params = get_privkey_info_params(owner_privkey_info) utxo_client = get_utxo_provider_client(config_path=config_path) insufficient_funds = False preorder_tx_fee = estimate_preorder_tx_fee( name, name_cost, owner_address, payment_address, utxo_client, owner_privkey_params=owner_privkey_params, min_payment_confs=min_payment_confs, config_path=config_path, include_dust=True ) if preorder_tx_fee is not None: preorder_tx_fee = int(preorder_tx_fee) else: # do our best preorder_tx_fee = get_tx_fee('00' * APPROX_PREORDER_TX_LEN, config_path=config_path) insufficient_funds = True return {'status': True, 'name_cost': name_cost, 'tx_fee': preorder_tx_fee, 'insufficient': insufficient_funds} except UTXOException as ue: log.error('Failed to query UTXO provider.') if BLOCKSTACK_DEBUG is not None: log.exception(ue) return {'error': 'Failed to query UTXO provider. Please try again.'} def _estimate_register_tx(): """ Estimate register tx cost Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success Return {'error': ...} on failure """ try: owner_privkey_params = get_privkey_info_params(owner_privkey_info) utxo_client = get_utxo_provider_client(config_path=config_path) insufficient_funds = False register_tx_fee = estimate_register_tx_fee( name, owner_address, payment_address, utxo_client, owner_privkey_params=owner_privkey_params, config_path=config_path, include_dust=True ) if register_tx_fee is not None: register_tx_fee = int(register_tx_fee) else: register_tx_fee = get_tx_fee('00' * APPROX_REGISTER_TX_LEN, config_path=config_path) insufficient_funds = True return {'status': True, 'tx_fee': register_tx_fee, 'insufficient': insufficient_funds} except UTXOException as ue: log.error('Failed to query UTXO provider.') if BLOCKSTACK_DEBUG is not None: log.exception(ue) return {'error': 'Failed to query UTXO provider. Please try again.'} def _estimate_update_tx(): """ Estimate update tx fee Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success Return {'error': ...} on failure """ try: owner_privkey_params = get_privkey_info_params(owner_privkey_info) utxo_client = get_utxo_provider_client(config_path=config_path) insufficient_funds = False estimate = False update_tx_fee = estimate_update_tx_fee( name, payment_privkey_info, owner_address, utxo_client, owner_privkey_params=owner_privkey_params, config_path=config_path, payment_address=payment_address, include_dust=True ) if update_tx_fee is not None: update_tx_fee = int(update_tx_fee) else: update_tx_fee = get_tx_fee('00' * APPROX_UPDATE_TX_LEN, config_path=config_path) insufficient_funds = True if payment_privkey_info is None: estimate = True return {'status': True, 'tx_fee': update_tx_fee, 'insufficient': insufficient_funds, 'estimate': estimate} except UTXOException as ue: log.error('Failed to query UTXO provider.') if BLOCKSTACK_DEBUG is not None: log.exception(ue) return {'error': 'Failed to query UTXO provider. Please try again.'} def _estimate_transfer_tx(): """ Estiamte transfer tx fee Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success Return {'error': ...} on failure """ try: if transfer_address is not None: owner_privkey_params = get_privkey_info_params(owner_privkey_info) utxo_client = get_utxo_provider_client(config_path=config_path) insufficient_funds = False estimate = False transfer_tx_fee = estimate_transfer_tx_fee( name, payment_privkey_info, owner_address, utxo_client, owner_privkey_params=owner_privkey_params, config_path=config_path, payment_address=payment_address, include_dust=True ) if transfer_tx_fee is not None: transfer_tx_fee = int(transfer_tx_fee) else: transfer_tx_fee = get_tx_fee('00' * APPROX_TRANSFER_TX_LEN, config_path=config_path) insufficient_funds = True if payment_privkey_info is None: estimate = True return {'status': True, 'tx_fee': transfer_tx_fee, 'insufficient': insufficient_funds, 'estimate': estimate} else: return {'error': 'No transfer address'} except UTXOException as ue: log.error('Failed to query UTXO provider.') if BLOCKSTACK_DEBUG is not None: log.exception(ue) return {'error': 'Failed to query UTXO provider. Please try again.'} def _estimate_revoke_tx(): """ Estimate revoke tx fee Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success Return {'error': ...} on failure """ try: owner_privkey_params = get_privkey_info_params(owner_privkey_info) utxo_client = get_utxo_provider_client(config_path=config_path) insufficient_funds = False estimate = False tx_fee = estimate_revoke_tx_fee( name, payment_privkey_info, owner_address, utxo_client, owner_privkey_params=owner_privkey_params, config_path=config_path, include_dust=True ) if tx_fee is not None: tx_fee = int(tx_fee) else: tx_fee = get_tx_fee('00' * APPROX_REVOKE_TX_LEN, config_path=config_path) insufficient_funds = True if payment_privkey_info is None: estimate = True return {'status': True, 'tx_fee': tx_fee, 'insufficient': insufficient_funds, 'estimate': estimate} except UTXOException as ue: log.error('Failed to query UTXO provider.') if BLOCKSTACK_DEBUG is not None: log.exception(ue) return {'error': 'Failed to query UTXO provider. Please try again.'} def _estimate_renewal_tx(): """ Estimate renewal tx fee Return {'status': True, 'name_cost': ..., 'tx_fee': ..., 'insufficient': ...} on success Return {'error': ...} on failure """ name_cost = None try: res = get_name_cost(name, proxy=proxy) name_cost = res['satoshis'] except Exception as e: log.exception(e) return {'error': 'Could not get name price'} try: owner_privkey_params = get_privkey_info_params(owner_privkey_info) utxo_client = get_utxo_provider_client(config_path=config_path) insufficient_funds = False estimate = False tx_fee = estimate_renewal_tx_fee( name, name_cost, payment_privkey_info, owner_privkey_info, utxo_client, config_path=config_path, include_dust=True ) if tx_fee is not None: tx_fee = int(tx_fee) else: tx_fee = get_tx_fee('00' * APPROX_RENEWAL_TX_LEN, config_path=config_path) insufficient_funds = True if payment_privkey_info is None: estimate = True return {'status': True, 'name_cost': name_cost, 'tx_fee': tx_fee, 'insufficient': insufficient_funds, 'estimate': estimate} except UTXOException as ue: log.error('Failed to query UTXO provider.') if BLOCKSTACK_DEBUG is not None: log.exception(ue) return {'error': 'Failed to query UTXO provider. Please try again.'} sg = scatter_gather assert sg tx_fee_checks = { 'preorder': _estimate_preorder_tx, 'register': _estimate_register_tx, 'update': _estimate_update_tx, 'transfer': _estimate_transfer_tx, 'revoke': _estimate_revoke_tx, 'renewal': _estimate_renewal_tx, } sg.add_task('get_balance', _get_balance) # queue each operation for op in set(operations): if op not in tx_fee_checks.keys(): log.error("Unrecognized operation {}".format(op)) return {'error': 'Unrecognized operation'} check_name = '{}_tx_fee'.format(op) sg.add_task( check_name, tx_fee_checks[op] ) # scatter/gather primed! return {'status': True} def interpret_operation_fees( operations, scatter_gather, balance=None ): """ Interpret the result of getting the tx fees required for a sequence of operations. Coalesce them into a grand total, and warn us if there is not enough balance to cover the cost. Return a dict with the following: * preorder_tx_fee * register_tx_fee * update_tx_fee * transfer_tx_fee * renewal_tx_fee * revoke_tx_fee * name_price * total_estimated_cost * warnings Return {'error': ...} if an operation failed """ insufficient_funds = False estimate = False total_cost = 0 total_tx_fees = 0 reply = {} results = scatter_gather.get_results() if balance is None: # extract from running operation sanity checks assert 'get_balance' in results balance = 0 if 'error' in results['get_balance'].keys(): log.error("Failed to get balance") else: balance = results['get_balance']['status'] log.debug("Balance is {} satoshis".format(balance)) failed_tasks = [] for task in operations: tx_fee_task = '{}_tx_fee'.format(task) task_res = results[tx_fee_task] if 'error' in task_res: failed_tasks.append(tx_fee_task) continue assert 'insufficient' in task_res, "Invalid task res: {}".format(task_res) assert 'tx_fee' in task_res, "Invalid task res: {}".format(task_res) if task_res['tx_fee'] is None: log.error("Task {} failed to get tx fee".format(task)) failed_tasks.append(tx_fee_task) continue insufficient_funds = insufficient_funds or task_res['insufficient'] estimate = estimate or task_res.get('estimate', False) reply[tx_fee_task] = int(task_res['tx_fee']) total_tx_fees += int(task_res['tx_fee']) log.debug("{} +{} satoshis (tx fee)".format(tx_fee_task, int(task_res['tx_fee']))) total_cost += int(task_res['tx_fee']) if 'name_cost' in task_res.keys(): log.debug("{} +{} satoshis (name cost)".format(tx_fee_task, int(task_res['name_cost']))) total_cost += int(task_res['name_cost']) reply['name_price'] = int(task_res['name_cost']) log.debug('Total cost of {} is {} satoshis'.format(','.join(operations), total_cost)) reply['total_tx_fees'] = total_tx_fees reply['total_estimated_cost'] = total_cost if total_cost > balance: log.debug("Insufficient funds: need {}, have {}".format(total_cost, balance)) insufficient_funds = True if insufficient_funds: reply['warnings'] = ['Insufficient funds; fees are rough estimates.'] if estimate: reply.setdefault('warnings', []) reply['warnings'].append('Wallet not accessed; fees are rough estimates.') if len(failed_tasks) > 0: log.error("Some fee-query tasks failed: {}".format(','.join(failed_tasks))) reply['error'] = 'Some fee-query tasks failed: {}'.format(','.join(failed_tasks)) return reply def check_operations( fqu, operations, owner_privkey_info, payment_privkey_info, required_checks=[], transfer_address=None, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that an operation sequence can be performed, given the set of sanity checks that must pass. Return {'status': True, 'opchecks': {...}} if so Return {'error': ...} if not. """ assert len(required_checks) > 0, "Missing required checks" log.debug("Check {} on {}: test {}".format(', '.join(operations), fqu, ', '.join(required_checks))) owner_address = get_privkey_info_address(owner_privkey_info) payment_address = get_privkey_info_address(payment_privkey_info) sg = ScatterGather() res = operation_sanity_checks(fqu, operations, sg, payment_privkey_info, owner_privkey_info, required_checks=required_checks, min_confirmations=min_payment_confs, config_path=config_path, transfer_address=transfer_address, proxy=proxy ) if 'error' in res: log.error("Failed to begin operation sanity checks: {}".format(res['error'])) return {'error': 'Failed to perform sanity checks'} sg.run_tasks() opchecks = interpret_operation_sanity_checks( fqu, operations, sg ) if 'error' in opchecks: log.error("Failed to interpret operation sanity checks") return {'error': 'Failed operation sanity checks:\n{}'.format(opchecks['error']), 'opchecks': opchecks} failed_checks = [] failed_check_errors = {} for res_name in required_checks + ['get_balance']: if 'error' in sg.get_result(res_name): log.debug("Task '{}' reports error: {}".format(res_name, sg.get_result(res_name)['error'])) failed_checks.append(res_name) failed_check_errors[res_name] = 'Reason: {}'.format(sg.get_result(res_name)['error']) if not sg.get_result(res_name)['status']: log.debug("Task '{}' did not succeed".format(res_name)) failed_checks.append(res_name) failed_check_errors[res_name] = '' if len(failed_checks) > 0: return {'error': 'Unable to {} name:\n{}'.format( ','.join(operations), '\n'.join([' * check "{}" failed. {}'.format(check, failed_check_errors.get(res_name, "")) for check in failed_checks]) ), 'opchecks': opchecks} balance = sg.get_result('get_balance')['status'] if balance < opchecks['total_estimated_cost']: msg = 'Address {} does not have enough balance (need {}, have {}).' msg = msg.format(payment_address, opchecks['total_estimated_cost'], balance) return {'error': msg, 'opchecks': opchecks} # checks pass! return {'status': True, 'opchecks': opchecks} def _check_op(fqu, operation, required_checks, owner_privkey_info, payment_privkey_info, transfer_address=None, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Check an operation Return {'status': True, 'opchecks': {...}, 'tx_fee': ...} on success Return {'error': ...} on error """ # find tx fee, and do sanity checks assert owner_privkey_info assert payment_privkey_info res = check_operations( fqu, [operation], owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, transfer_address=transfer_address, required_checks=required_checks, config_path=config_path, proxy=proxy ) opchecks = res.get('opchecks', None) tx_fee = None if opchecks: tx_fee = opchecks.get('total_tx_fees', None) if 'error' in res: return {'error': res['error'], 'tx_fee': tx_fee, 'opchecks': opchecks} else: return {'status': True, 'tx_fee': tx_fee, 'opchecks': opchecks} def check_preorder(fqu, cost_satoshis, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that a preorder can go through. Return {'status': True, 'opchecks': {...}, 'tx_fee': ...} """ # find tx fee, and do sanity checks assert owner_privkey_info assert payment_privkey_info required_checks = ['is_name_available', 'is_payment_address_usable'] res = check_operations( fqu, ['preorder'], owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, required_checks=required_checks, config_path=config_path, proxy=proxy ) opchecks = res.get('opchecks', None) tx_fee = None if opchecks: tx_fee = opchecks.get('total_tx_fees', None) if 'error' in res: return {'error': res['error'], 'opchecks': res.get('opchecks', None), 'tx_fee': res.get('opchecks', {}).get('total_tx_fees', None)} if cost_satoshis is not None: if opchecks['name_price'] > cost_satoshis: return {'error': 'Invalid cost: expected {}, got {}'.format(opchecks['name_price'], cost_satoshis), 'tx_fee': tx_fee, 'opchecks': opchecks} return {'status': True, 'tx_fee': tx_fee, 'opchecks': opchecks} def check_register(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that a register can go through """ required_checks = ['is_name_available', 'owner_can_receive', 'is_payment_address_usable'] return _check_op(fqu, 'register', required_checks, owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy ) def check_update(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that an update can go through """ required_checks = ['is_name_registered', 'is_owner_address_usable', 'is_payment_address_usable', 'is_name_owner'] return _check_op(fqu, 'update', required_checks, owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy) def check_transfer(fqu, transfer_address, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that a transfer can go through """ required_checks = ['is_name_registered', 'is_owner_address_usable', 'is_payment_address_usable', 'is_name_owner', 'recipient_can_receive'] return _check_op(fqu, 'transfer', required_checks, owner_privkey_info, payment_privkey_info, transfer_address=transfer_address, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy) def check_renewal(fqu, renewal_fee, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that a renew can go through """ # find tx fee, and do sanity checks assert owner_privkey_info assert payment_privkey_info required_checks = ['is_name_registered', 'is_name_owner', 'is_owner_address_usable', 'is_payment_address_usable'] res = check_operations( fqu, ['renewal'], owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, required_checks=required_checks, config_path=config_path, proxy=proxy ) opchecks = res.get('opchecks', None) tx_fee = None if opchecks: tx_fee = opchecks.get('total_tx_fees', None) if 'error' in res: return {'error': res['error'], 'opchecks': res.get('opchecks', None), 'tx_fee': res.get('opchecks', {}).get('total_tx_fees', None)} if renewal_fee is not None: if opchecks['name_price'] > renewal_fee: return {'error': 'Invalid cost: expected {}, got {}'.format(opchecks['name_price'], renewal_fee), 'tx_fee': tx_fee, 'opchecks': opchecks} return {'status': True, 'tx_fee': tx_fee, 'opchecks': opchecks} def check_revoke(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ): """ Verify that a revoke can go through """ required_checks = ['is_name_registered', 'is_name_owner', 'is_owner_address_usable', 'is_payment_address_usable'] return _check_op(fqu, 'revoke', required_checks, owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy )