def get_block_by_hash(self, block_hash): url = self.url + '/block/{hash}'.format(hash=block_hash) LOG.info('GET %s' % url) try: r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get block %s from Blockstream.info: %s' % (block_hash, ex)) return { 'error': 'Unable to get block %s from Blockstream.info' % block_hash } if all(key in data for key in ('height', 'id', 'timestamp', 'merkle_root', 'size')): block = { 'height': data['height'], 'hash': data['id'], 'time': data['timestamp'], 'merkleroot': data['merkle_root'], 'size': data['size'] } # Todo weight? return {'block': block} else: return {'error': 'Received invalid data: %s' % data}
def get_block_by_hash(self, block_hash): url = '{api_url}/block/{hash}'.format(api_url=self.url, hash=block_hash) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get block %s from Blocktrail.com: %s' % (block_hash, ex)) return { 'error': 'Unable to get block %s from Blocktrail.com' % block_hash } data = data['data'] if data['data'] is not None else {} if all(key in data for key in ('height', 'hash', 'timestamp', 'mrkl_root', 'size')): block = { 'height': data['height'], 'hash': data['hash'], 'time': data['timestamp'], 'merkleroot': data['mrkl_root'], 'size': data['size'] } return {'block': block} else: return {'error': 'Received invalid data: %s' % data}
def get_block_by_hash(self, block_hash): url = self.url + '/block/' + block_hash try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get block %s from %s: %s' % (block_hash, self.url, ex)) return { 'error': 'Unable to get block %s from %s' % (block_hash, self.url) } block = {} if all(key in data for key in ('height', 'hash', 'time', 'merkleroot', 'size')): block['height'] = data['height'] block['hash'] = data['hash'] block['time'] = data['time'] block['merkleroot'] = data['merkleroot'] block['size'] = data['size'] return {'block': block} else: return {'error': 'Received invalid data: %s' % data}
def get_prime_input_address(self, txid): url = self.url + '/tx/' + str(txid) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error( 'Unable to get prime input address of transaction %s from %s: %s' % (txid, self.url, ex)) return { 'error': 'Unable to get prime input address of transaction %s from %s' % (txid, self.url) } if 'vin' in data: tx_inputs = data['vin'] input_addresses = [] for i in range(0, len(tx_inputs)): input_addresses.append(tx_inputs[i]['addr']) if len(input_addresses) > 0: prime_input_address = sorted(input_addresses)[0] return {'prime_input_address': prime_input_address} return {'error': 'Received invalid data: %s' % data}
def get_utxos(self, address, confirmations=3): url = self.url + '/addrs/' + address + '/utxo?noCache=1' try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get utxos of address %s from %s: %s' % (address, url, ex)) return { 'error': 'Unable to get utxos of address %s from %s' % (address, url) } utxos = [] for output in data: if all(key in output for key in ('confirmations', 'txid', 'vout', 'satoshis', 'scriptPubKey')): utxo = { 'confirmations': output['confirmations'], 'output_hash': output['txid'], 'output_n': output['vout'], 'value': output['satoshis'], 'script': output['scriptPubKey'] } if utxo['confirmations'] >= confirmations: utxos.append(utxo) return { 'utxos': sorted(utxos, key=lambda k: (k['confirmations'], k['output_hash'], k['output_n'])) }
def get_balance(self, address): url = '{api_url}/address/{address}'.format(api_url=self.url, address=address) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error( 'Unable to get balance of address %s from Blocktrail.com: %s' % (address, ex)) return { 'error': 'Unable to get balance of address %s from Blocktrail.com' % address } data = data['data'] if data['data'] is not None else {} if all(key in data for key in ('balance', 'received', 'sent')): balance = { 'final': data['balance'] - data['unconfirmed_received'] + data['unconfirmed_sent'], 'received': data['received'] - data['unconfirmed_received'], 'sent': data['sent'] - data['unconfirmed_sent'] } return {'balance': balance} else: return {'error': 'Received invalid data: %s' % data}
def initialize_database(database, tables, user, password): # Try to make a connection, if the database does not exist yet, create the database and create the tables in it """ Check if a database exists, if not create it :param database: The name of the database (string) :param tables: A dict containing the sql statements to create the tables (dict) :param user: The username for the database (string) :param password: The password for the database (string) """ cnx = mysql.connector.connect(user=user, password=password) cursor = cnx.cursor() try: cursor.execute("USE {}".format(database)) except mysql.connector.Error as err: LOG.info("Database {} does not exists.".format(database)) if err.errno == errorcode.ER_BAD_DB_ERROR: create_database(cursor=cursor, database=database) LOG.info("Database {} created successfully.".format(database)) cnx.database = database create_tables(cursor=cursor, tables=tables) else: LOG.error(err) cursor.close() cnx.close()
def construct_transaction_inputs(self): """ Construct a list of dict object containing the necessary information for the inputs of a transaction :return: A list of dicts containing the following keys for each utxo: 'address', 'value', 'output' and 'confirmations' """ if self.unspent_outputs is not None and len(self.unspent_outputs) > 0: LOG.info('Found %s utxos for address %s' % (len(self.unspent_outputs), self.sending_address)) else: LOG.error('No utxos found for address %s' % self.sending_address) # Construct the transaction inputs tx_inputs = [ { 'address': utxo.address, 'value': utxo.value, 'output': utxo.output, # output needs to be formatted as txid:i 'confirmations': utxo.confirmations } for utxo in self.unspent_outputs ] return tx_inputs
def get_balance(self, address): url = self.url + '/address/{address}'.format(address=address) LOG.info('GET %s' % url) try: r = requests.get(url) data = r.json() except Exception as ex: LOG.error( 'Unable to get address info for %s from Blockstream.info: %s' % (address, ex)) return { 'error': 'Unable to get address info for %s from Blockstream.info' % address } sent_balance = data['chain_stats'][ 'spent_txo_sum'] # Todo fix the sent and received balance because blockstream reports this wrong (also counts when change is sent back to the address itself) received_balance = data['chain_stats']['funded_txo_sum'] final_balance = received_balance - sent_balance balance = { 'final': final_balance, 'received': received_balance, 'sent': sent_balance } return {'balance': balance}
def get_block_by_height(self, height): url = '{api_url}/get_block/{network}/{height}'.format( api_url=self.url, network=self.network, height=height) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get block %s from Chain.so: %s' % (height, ex)) return {'error': 'Unable to get block %s from Chain.so' % height} if 'data' not in data: LOG.error('Invalid response data from Chain.so: %s' % data) return {'error': 'Invalid response data from Chain.so: %s' % data} data = data['data'] if all(key in data for key in ('block_no', 'blockhash', 'time', 'merkleroot', 'size')): block = { 'height': data['block_no'], 'hash': data['blockhash'], 'time': data['time'], 'merkleroot': data['merkleroot'], 'size': data['size'] } return {'block': block} else: return {'error': 'Received invalid data: %s' % data}
def get_prime_input_address(self, txid): url = '{api_url}/rawtx/{txid}'.format(api_url=self.url, txid=txid) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error( 'Unable to get prime input address of tx %s from Blockchain.info: %s' % (txid, ex)) return { 'error': 'Unable to get prime input address of tx %s from Blockchain.info' % txid } if 'inputs' in data: tx_inputs = data['inputs'] input_addresses = [] for i in range(0, len(tx_inputs)): if 'prev_out' in tx_inputs[ i]: # Coinbase transactions don't have a input address input_addresses.append(tx_inputs[i]['prev_out']['addr']) if len(input_addresses) > 0: prime_input_address = sorted(input_addresses)[0] return {'prime_input_address': prime_input_address} else: # transaction was a coinbase transaction, so there are no input addresses return {'prime_input_address': None} return {'error': 'Received invalid data: %s' % data}
def get_balance(self, address): url = '{api_url}/address/{network}/{address}'.format( api_url=self.url, network=self.network, address=address) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get balance of address %s from Chain.so: %s' % (address, ex)) return { 'error': 'Unable to get balance of address %s from Chain.so' % address } if 'data' not in data: LOG.error('Invalid response data from Chain.so: %s' % data) return {'error': 'Invalid response data from Chain.so: %s' % data} data = data['data'] final_balance = btc2satoshis(btc=data['balance']) received_balance = btc2satoshis(btc=data['received_value']) sent_balance = received_balance - final_balance balance = { 'final': final_balance, 'received': received_balance, 'sent': sent_balance } return {'balance': balance}
def process_ipfs_hash(self, ipfs_hash): LOG.info('Retrieving IPFS object') if self.ipfs_object is not None: LOG.info( 'IPFS object given with request, uploading data to local IPFS node to check that hashes are equal' ) local_ipfs_hash = add_json(data=self.ipfs_object) if ipfs_hash != local_ipfs_hash: LOG.error( 'Supplied object does not correspond to the given IPFS hash: %s != %s' % (ipfs_hash, local_ipfs_hash)) return try: data = get_json(cid=ipfs_hash) if isinstance(data, dict): self.json = data elif isinstance(data, str): self.json = simplejson.loads(data) else: raise Exception( 'IPFS hash does not contain a dict or a json string: %s -> %s' % (ipfs_hash, data)) LOG.info('Message contains json data: %s' % self.json) except Exception as ex: LOG.error('IPFS hash does not contain valid json data: %s' % ex) return
def get_block_by_height(self, height): url = '{api_url}/block-height/{height}?format=json'.format( api_url=self.url, height=height) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get block %s from Blockchain.info: %s' % (height, ex)) return { 'error': 'Unable to get block %s from Blockchain.info' % height } if 'blocks' in data: blocks = data['blocks'] for i in range(0, len(blocks)): if blocks[i]['main_chain'] is True and blocks[i][ 'height'] == height: block = { 'height': blocks[i]['height'], 'hash': blocks[i]['hash'], 'time': blocks[i]['time'], 'merkleroot': blocks[i]['mrkl_root'], 'size': blocks[i]['size'] } return {'block': block} return {'error': 'Received invalid data: %s' % data}
def exit_with_error(self, message): """ Log an error message and set the http response with the same error message :param message: The error message """ LOG.error(message) self.http_response = {'error': message}
def add_str(string): global IPFS_API try: cid = IPFS_API.add_str(string=string) except Exception as e: LOG.error('Unable to store string on IPFS: %s' % e) raise Exception('IPFS failure') return CID(cid).__str__()
def add_file(filename): global IPFS_API try: ipfs_info = IPFS_API.add(filename) except Exception as e: LOG.error('Unable to store file on IPFS: %s' % e) raise Exception('IPFS failure') return ipfs_info['Hash'], ipfs_info['Name'], ipfs_info['Size']
def run(self, *args, **kwargs): LOG.info('Running Spellbook Script: %s' % os.path.splitext(os.path.basename(__file__))[0]) if 'message' not in self.json: LOG.error('key "message" not found in http POST request') return message = self.json['message'] if not valid_op_return(message=message): LOG.error( 'Can not create Notary request: message is not valid to put in a OP_RETURN output: %s' % message) return # Use the number of times the trigger has been triggered as a identifier for the request and as the index of the address in the hot wallet request_id = self.triggered + 1 # Get the address to receive payment request_address = get_address_from_wallet(account=BIP44_ACCOUNT, index=request_id) # Create a new action to send a custom transaction with the OP_RETURN data action_id = 'Notary-send-tx_%s' % request_id action = get_action(action_id=action_id, action_type=ActionType.SENDTRANSACTION) action.transaction_type = TransactionType.SEND2SINGLE action.wallet_type = WALLET_TYPE action.bip44_account = BIP44_ACCOUNT action.bip44_index = request_id action.receiving_address = get_address_from_wallet( account=BIP44_ACCOUNT, index=0 ) # always use the first address in the account to receive the transactions action.op_return_data = message action.save() # Create a new trigger that activates when payment is received invoice_paid_trigger_id = 'Notary-payment_%s' % request_id trigger = get_trigger(trigger_id=invoice_paid_trigger_id, trigger_type=TriggerType.BALANCE) trigger.address = request_address trigger.amount = NOTARY_COST trigger.actions = [action_id] trigger.status = 'Active' trigger.self_destruct = int(time.time()) + REQUEST_TIMEOUT trigger.destruct_actions = True trigger.save() self.http_response = { 'request_id': request_id, 'address': request_address, 'value': NOTARY_COST, 'timeout': int(time.time()) + REQUEST_TIMEOUT }
def get_transaction(self, txid): url = '{api_url}/tx/{txid}?verbose=3'.format(api_url=self.url, txid=txid) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get transaction %s from BTC.com: %s' % (txid, ex)) return { 'error': 'Unable to get transaction %s from BTC.com' % txid } data = data['data'] if data['data'] is not None else {} # todo check key names , test by setting testnet wrong on explorers tx = TX() tx.txid = txid tx.wtxid = data['witness_hash'] tx.lock_time = data['lock_time'] tx.block_height = data[ 'block_height'] if 'block_height' in data and data[ 'block_height'] != -1 else None tx.confirmations = data[ 'confirmations'] if 'confirmations' in data else None for item in data['inputs']: tx_input = TxInput() tx_input.address = item['prev_addresses'][0] if len( item['prev_addresses']) > 0 else None tx_input.value = item['prev_value'] tx_input.txid = item['prev_tx_hash'] tx_input.n = item['prev_position'] if item[ 'prev_position'] is not -1 else None tx_input.script = item['script_hex'] tx_input.sequence = item['sequence'] tx.inputs.append(tx_input) for i, item in enumerate(data['outputs']): tx_output = TxOutput() tx_output.address = item['addresses'][0] if len( item['addresses']) > 0 else None tx_output.value = item['value'] tx_output.n = i tx_output.spent = False if item['spent_by_tx'] is None else True tx_output.script = item['script_hex'] if item['script_hex'][:2] == '6a': tx_output.op_return = tx.decode_op_return(item['script_hex']) tx.outputs.append(tx_output) return {'transaction': tx.json_encodable()}
def get_utxos(self, address, confirmations=3): url = self.url + '/blocks/tip/height' LOG.info('GET %s' % url) try: r = requests.get(url) latest_block_height = int(r.text) except Exception as ex: LOG.error( 'Unable to get latest block_height from Blockstream.info: %s' % ex) return { 'error': 'Unable to get latest block_height from Blockstream.info' } url = self.url + '/address/{address}/utxo'.format(address=address) LOG.info('GET %s' % url) try: r = requests.get(url) data = r.json() except Exception as ex: LOG.error( 'Unable to get address utxos for %s from Blockstream.info: %s' % (address, ex)) return { 'error': 'Unable to get utxos info for %s from Blockstream.info' % address } LOG.info('Got %s utxos' % len(data)) utxos = [] for output in data: confirmations = latest_block_height - int( output['status']['block_height'] ) + 1 if output['status']['confirmed'] is True else 0 utxo = { 'confirmations': confirmations, 'output_hash': output['txid'], 'output_n': output['vout'], 'value': output['value'], 'script': None } # Blockstream.info does not provide the script for utxos if utxo['confirmations'] >= confirmations: utxos.append(utxo) return { 'utxos': sorted(utxos, key=lambda k: (k['confirmations'], k['output_hash'], k['output_n'])) }
def connect_to_ipfs(): global IPFS_API # Check if IPFS node is running multi_address = '/ip4/{host}/tcp/{port}/http'.format( host=get_ipfs_api_host(), port=get_ipfs_api_port()) LOG.info('Trying to connect with IPFS on %s' % multi_address) try: IPFS_API = ipfshttpclient.connect(multi_address) LOG.info('Connected with IPFS') except Exception as ex: LOG.error('IPFS node is not running: %s' % ex)
def get_transaction(self, txid): url = self.url + '/tx/' + str(txid) try: LOG.info('GET %s' % url) r = requests.get(url) data = r.json() except Exception as ex: LOG.error('Unable to get transaction %s from %s: %s' % (txid, self.url, ex)) return { 'error': 'Unable to get transaction %s from %s' % (txid, self.url) } tx = TX() tx.txid = txid tx.block_height = data['blockheight'] if 'blockheight' in data else None tx.lock_time = data['locktime'] for item in data['vin']: tx_input = TxInput() tx_input.address = item['addr'] if 'addr' in item else None tx_input.value = item['valueSat'] if 'valueSat' in item else 0 tx_input.txid = item['txid'] if 'txid' in item else None tx_input.n = item['n'] if 'coinbase' not in item else None tx_input.script = item['scriptSig'][ 'hex'] if 'scriptSig' in item else None if 'coinbase' in item: tx_input.script = item['coinbase'] tx_input.sequence = item['sequence'] tx.inputs.append(tx_input) for item in data['vout']: tx_output = TxOutput() tx_output.address = item['scriptPubKey']['addresses'][ 0] if 'addresses' in item['scriptPubKey'] else None tx_output.value = int(float(item['value']) * 1e8) tx_output.n = item['n'] tx_output.spent = True if 'spentTxId' in item and item[ 'spentTxId'] is not None else False tx_output.script = item['scriptPubKey']['hex'] if item['scriptPubKey']['hex'][:2] == '6a': tx_output.op_return = tx.decode_op_return( item['scriptPubKey']['hex']) tx.outputs.append(tx_output) tx.confirmations = data[ 'confirmations'] if 'confirmations' in data else None return {'transaction': tx.json_encodable()}
def parse_transaction(self, data, latest_block_height=None): if latest_block_height is None: url = self.url + '/blocks/tip/height' LOG.info('GET %s' % url) try: r = requests.get(url) latest_block_height = int(r.text) except Exception as ex: LOG.error( 'Unable to get latest block_height from Blockstream.info: %s' % ex) return { 'error': 'Unable to get latest block_height from Blockstream.info' } tx = TX() tx.txid = data['txid'] tx.lock_time = data['locktime'] tx.block_height = data['status'][ 'block_height'] if 'block_height' in data['status'] else None tx.confirmations = latest_block_height - tx.block_height + 1 if tx.block_height is not None else 0 for item in data['vin']: tx_input = TxInput() tx_input.address = item['prevout']['scriptpubkey_address'] if item[ 'prevout'] is not None else None tx_input.value = item['prevout']['value'] if item[ 'prevout'] is not None else 0 tx_input.n = item['vout'] if item['is_coinbase'] is False else None tx_input.txid = item['txid'] tx_input.script = item['scriptsig'] tx_input.sequence = item['sequence'] tx.inputs.append(tx_input) for i, item in enumerate(data['vout']): tx_output = TxOutput() tx_output.address = item[ 'scriptpubkey_address'] if 'scriptpubkey_address' in item else None tx_output.value = item['value'] tx_output.n = i tx_output.spent = None # Blockstream does not provide information if a tx output has been spent tx_output.script = item['scriptpubkey'] if item['scriptpubkey'][:2] == '6a': tx_output.op_return = tx.decode_op_return(item['scriptpubkey']) tx.outputs.append(tx_output) return tx
def create_database(cursor, database): """ Create a new database :param cursor: A MySQL cursor object :param database: The name of the database (string) """ LOG.info('Creating database %s' % database) try: cursor.execute( "CREATE DATABASE {} DEFAULT CHARACTER SET 'utf8'".format(database)) except mysql.connector.Error as err: LOG.error("Failed creating database: {}".format(err))
def load_from_json_file(filename): """ Load data from a json file :return: a dict containing the data from the json file """ data = None # Load the json file with open(filename, 'r') as input_file: try: data = simplejson.load(input_file) except Exception as ex: LOG.error('Failed to load %s: %s' % (filename, ex)) return data
def push_tx(self, tx): url = self.url + '/broadcast?tx={tx}'.format(tx=tx) LOG.info('GET %s' % url) try: r = requests.get(url) except Exception as ex: LOG.error('Unable to push tx via Blockstream.info: %s' % ex) return {'error': 'Unable to push tx Blockstream.info: %s' % ex} data = r.text.strip() if r.status_code == 200: return {'success': True, 'txid': data} else: LOG.error('Unable to push tx via Blockstream.info: %s' % data) return {'error': 'Unable to push tx Blockstream.info: %s' % data}
def get_block_by_height(self, height): url = self.url + '/block-height/{height}'.format(height=height) LOG.info('GET %s' % url) try: r = requests.get(url) block_hash = r.text except Exception as ex: LOG.error('Unable to get block %s from Blockstream.info: %s' % (height, ex)) return { 'error': 'Unable to get block %s from Blockstream.info' % height } return self.get_block_by_hash(block_hash=block_hash)
def push_tx(self, tx): url = '{api_url}/pushtx'.format(api_url=self.url) LOG.info('POST %s' % url) try: r = requests.post(url, data=dict(tx=tx)) except Exception as ex: LOG.error('Unable to push tx via Blockchain.info: %s' % ex) return {'error': 'Unable to push tx Blockchain.info: %s' % ex} data = r.text.strip() if r.status_code == 200 and data == 'Transaction Submitted': return {'success': True} else: LOG.error('Unable to push tx via Blockchain.info: %s' % data) return {'error': 'Unable to push tx Blockchain.info: %s' % data}
def _log_to_logger(*args, **kwargs): start_time = int(round(time.time() * 1000)) request_time = datetime.now() # Log information about the request before it is processed for debugging purposes REQUESTS_LOG.info('%s | %s | %s | %s' % (request_time, request.remote_addr, request.method, request.url)) if request.headers is not None: for key, value in request.headers.items(): REQUESTS_LOG.info(' HEADERS | %s: %s' % (key, value)) if request.json is not None: for key, value in request.json.items(): REQUESTS_LOG.info(' BODY | %s: %s' % (key, value)) actual_response = response try: actual_response = fn(*args, **kwargs) except Exception as ex: response_status = '500 ' + str(ex) LOG.error('%s caused an exception: %s' % (request.url, ex)) error_traceback = traceback.format_exc() for line in error_traceback.split('\n'): LOG.error(line) if get_mail_on_exception() is True: variables = {'HOST': get_host(), 'TRACEBACK': error_traceback} body_template = os.path.join('server_exception') sendmail(recipients=get_notification_email(), subject='Exception occurred @ %s' % get_host(), body_template=body_template, variables=variables) else: response_status = response.status end_time = int(round(time.time() * 1000)) REQUESTS_LOG.info('%s | %s | %s | %s | %s | %s ms' % (request_time, request.remote_addr, request.method, request.url, response_status, end_time - start_time)) return actual_response
def save_to_json_file(filename, data): """ Save data to a json file :param filename: The filename of the json file :param data: A dict containing the data to save (must be json-encodable) """ # Make sure the destination directory exists if not os.path.isdir(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) try: with open(filename, 'w') as output_file: simplejson.dump(data, output_file, indent=4, sort_keys=True) except Exception as ex: LOG.error('Failed to save data to json file %s: %s' % (filename, ex))