def get_fees( inputs, outputs ): """ Given a transaction's outputs, look up its fees: * the first output should be an OP_RETURN with the transfer info * the second output should be the new owner's address, with a DEFAULT_DUST_FEE * the third output should be the change address Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ if len(outputs) != 3: return (None, None) # 0: op_return if not tx_output_is_op_return( outputs[0] ): return (None, None) if outputs[0]["value"] != 0: return (None, None) # 1: transfer address if script_hex_to_address( outputs[1]["script_hex"] ) is None: return (None, None) # 2: change address if script_hex_to_address( outputs[2]["script_hex"] ) is None: return (None, None) dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = DEFAULT_DUST_FEE return (dust_fee, op_fee)
def get_fees(inputs, outputs): """ Given a transaction's outputs, look up its fees: * the first output should be an OP_RETURN with the transfer info * the second output should be the new owner's address, with a DEFAULT_DUST_FEE * the third output should be the change address Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ if len(outputs) != 3: return (None, None) # 0: op_return if not tx_output_is_op_return(outputs[0]): return (None, None) if outputs[0]["value"] != 0: return (None, None) # 1: transfer address if script_hex_to_address(outputs[1]["script_hex"]) is None: return (None, None) # 2: change address if script_hex_to_address(outputs[2]["script_hex"]) is None: return (None, None) dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = DEFAULT_DUST_FEE return (dust_fee, op_fee)
def get_fees( inputs, outputs ): """ Given a transaction's outputs, look up its fees: * the first output must be an OP_RETURN, and it must have a fee of 0. * the second output must be the reveal address, and it must have a dust fee * the third must be the change address * the fourth, if given, must be a burned fee sent to the burn address Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ dust_fee = 0 op_fee = 0 if len(outputs) != 3 and len(outputs) != 4: log.debug("len(outputs) == %s" % len(outputs)) return (None, None) # 0: op_return if not tx_output_is_op_return( outputs[0] ): log.debug("output[0] is not an OP_RETURN") return (None, None) # 1: reveal address if script_hex_to_address( outputs[1]["script_hex"] ) is None: log.debug("output[1] is not a p2pkh script") return (None, None) # 2: change address if script_hex_to_address( outputs[2]["script_hex"] ) is None: log.debug("output[2] is not a p2pkh script") return (None, None) # 3: burn address, if given if len(outputs) == 4: addr_hash = script_hex_to_address( outputs[3]["script_hex"] ) if addr_hash is None: log.debug("output[3] is not a p2pkh script") return (None, None) if addr_hash != BLOCKSTACK_BURN_ADDRESS: log.debug("output[3] is not the burn address (got %s)" % addr_hash) return (None, None) dust_fee = (len(inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = outputs[3]["value"] else: dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE return (dust_fee, op_fee)
def get_fees( inputs, outputs ): """ Given a transaction's outputs, look up its fees: * the first output must be an OP_RETURN, and it must have a fee of 0. * the second output must be the reveal address, and it must have a dust fee * the third must be the change address * the fourth, if given, must be a burned fee sent to the burn address Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ dust_fee = 0 op_fee = 0 if len(outputs) != 3 and len(outputs) != 4: return (None, None) # 0: op_return if not tx_output_is_op_return( outputs[0] ): return (None, None) if outputs[0]["value"] != 0: return (None, None) # 1: reveal address if script_hex_to_address( outputs[1]["script_hex"] ) is None: return (None, None) # 2: change address if script_hex_to_address( outputs[2]["script_hex"] ) is None: return (None, None) # 3: burn address, if given if len(outputs) == 4: addr_hash = script_hex_to_address( outputs[3]["script_hex"] ) if addr_hash is None: return (None, None) if addr_hash != BLOCKSTORE_BURN_PUBKEY_HASH: return (None, None) dust_fee = (len(inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = outputs[3]["value"] else: dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE return (dust_fee, op_fee)
def get_fees(inputs, outputs): """ Given a transaction's outputs, look up its fees: * there should be two outputs: the OP_RETURN and change address Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ if len(outputs) != 2: return (None, None) # 0: op_return if not tx_output_is_op_return(outputs[0]): return (None, None) if outputs[0]["value"] != 0: return (None, None) # 1: change address if script_hex_to_address(outputs[1]["script_hex"]) is None: return (None, None) dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = 0 return (dust_fee, op_fee)
def get_fees( inputs, outputs ): """ Given a transaction's outputs, look up its fees: * there should be two outputs: the OP_RETURN and change address Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ if len(outputs) != 2: return (None, None) # 0: op_return if not tx_output_is_op_return( outputs[0] ): return (None, None) if outputs[0]["value"] != 0: return (None, None) # 1: change address if script_hex_to_address( outputs[1]["script_hex"] ) is None: return (None, None) dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = 0 return (dust_fee, op_fee)
def get_fees( inputs, outputs ): """ Given a transaction's outputs, look up its fees: * the first output must be an OP_RETURN, and it must have a fee of 0. # the second must be the change address * the third must be a burn fee to the burn address. Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ if len(outputs) != 3: log.debug("Expected 3 outputs; got %s" % len(outputs)) return (None, None) # 0: op_return if not tx_output_is_op_return( outputs[0] ): log.debug("outputs[0] is not an OP_RETURN") return (None, None) if outputs[0]["value"] != 0: log.debug("outputs[0] has value %s'" % outputs[0]["value"]) return (None, None) # 1: change address if script_hex_to_address( outputs[1]["script_hex"] ) is None: log.error("outputs[1] has no decipherable change address") return (None, None) # 2: burn address addr_hash = script_hex_to_address( outputs[2]["script_hex"] ) if addr_hash is None: log.error("outputs[2] has no decipherable burn address") return (None, None) if addr_hash != BLOCKSTORE_BURN_ADDRESS: log.error("outputs[2] is not the burn address") return (None, None) dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = outputs[2]["value"] return (dust_fee, op_fee)
def get_fees(inputs, outputs): """ Given a transaction's outputs, look up its fees: * the first output must be an OP_RETURN, and it must have a fee of 0. # the second must be the change address * the third must be a burn fee to the burn address. Return (dust fees, operation fees) on success Return (None, None) on invalid output listing """ if len(outputs) != 3: log.debug("Expected 3 outputs; got %s" % len(outputs)) return (None, None) # 0: op_return if not tx_output_is_op_return(outputs[0]): log.debug("outputs[0] is not an OP_RETURN") return (None, None) if outputs[0]["value"] != 0: log.debug("outputs[0] has value %s'" % outputs[0]["value"]) return (None, None) # 1: change address if script_hex_to_address(outputs[1]["script_hex"]) is None: log.error("outputs[1] has no decipherable change address") return (None, None) # 2: burn address addr_hash = script_hex_to_address(outputs[2]["script_hex"]) if addr_hash is None: log.error("outputs[2] has no decipherable burn address") return (None, None) if addr_hash != BLOCKSTORE_BURN_ADDRESS: log.error("outputs[2] is not the burn address") return (None, None) dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE op_fee = outputs[2]["value"] return (dust_fee, op_fee)
def script_hex_to_address( script_hex ): """ Examine a scriptPubkey and extract an address. """ if script_hex.startswith("76a914") and script_hex.endswith("88ac") and len(script_hex) == 50: # p2pkh script return pybitcoin.script_hex_to_address( script_hex, version_byte=version_byte ) elif script_hex.startswith("a914") and script_hex.endswith("87") and len(script_hex) == 46: # p2sh script return bitcoin.script_to_address( script_hex, vbyte=multisig_version_byte ) else: raise ValueError("Nonstandard script %s" % script_hex)
def script_hex_address(script_hex): """ Examine a scriptPubkey and extract an address """ if script_hex.startswith("76a914") and script_hex.endswith("88ac") and len( script_hex) == 50: # p2pkh script return pybitcoin.script_hex_to_address(script_hex, version_byte=version_byte) elif script_hex.startswith("a914") and script_hex.endswith("87") and len( script_hex) == 46: # p2sh script return bitcoin.script_to_address(script_hex, vbyte=multisig_version_byte) else: raise ValueError("Nonstandard script %s" % script_hex)
def btc_decoderawtransaction_compat( tx_hex ): """ Implementation of bitcoind's decoderawtransaction JSONRPC method. Tries to be faithful enough to bitcoind for virtualchain's sake. Does NOT handle coinbase transactions """ inputs, outputs, locktime, version = tx_deserialize( tx_hex ) txid = make_txid( tx_hex ) vin = [] vout = [] for inp in inputs: vin_inp = { "txid": inp['transaction_hash'], "vout": inp['output_index'], } if inp.has_key("script_sig"): scriptsig_hex = inp['script_sig'] scriptsig_asm = btc_decoderawtransaction_script_hex_to_asm( scriptsig_hex ) vin_inp['scriptSig'] = { 'asm': scriptsig_asm, 'hex': scriptsig_hex } if inp.has_key("sequence"): vin_inp['sequence'] = inp['sequence'] vin.append( vin_inp ) for i in xrange( 0, len(outputs) ): out = outputs[i] script_type = btc_decoderawtransaction_get_script_type( out['script_hex'] ) addresses = [] if script_type == "pubkeyhash": addresses.append( pybitcoin.script_hex_to_address( out['script_hex'] ) ) elif script_type == "pubkey": pubkey = btc_decoderawtransaction_get_pubkey_from_script( out['script_hex'] ) addr = pybitcoin.BitcoinPublicKey( pubkey ).address() addresses.append( addr ) elif script_type == "scripthash": script_hash = btc_decoderawtransaction_get_script_hash_from_script( out['script_hex'] ) addr = pybitcoin.b58check_encode( binascii.unhexlify( script_hash ), version_byte=5 ) addresses.append( addr ) vout_out = { "value": float(out['value']) / 10e7, "mock_bitcoind_value_satoshi": out['value'], # NOTE: extra "n": i, "scriptPubKey": { 'asm': btc_decoderawtransaction_script_hex_to_asm( out['script_hex'] ), 'hex': out['script_hex'], "type": script_type }, } if script_type in ["pubkeyhash", "pubkey", "scripthash"]: vout_out['scriptPubKey']['reqSigs'] = 1 if len(addresses) > 0: vout_out['scriptPubKey']['addresses'] = addresses vout.append( vout_out ) tx_decoded = { "txid": txid, "version": version, "locktime": locktime, "vin": vin, "vout": vout } return tx_decoded
def db_parse( block_id, opcode, data, senders, inputs, outputs, fee, db_state=None ): """ (required by virtualchain state engine) Parse a blockstore operation from a transaction's nulldata (data) and a list of outputs, as well as optionally the list of transaction's senders and the total fee paid. Return a parsed operation, and will also optionally have: * "sender": the first (primary) sender's script_pubkey. * "address": the sender's bitcoin address * "fee": the total fee paid for this record. * "recipient": the first non-OP_RETURN output's script_pubkey. * "sender_pubkey": the sender's public key (hex string), if this is a p2pkh transaction Return None on error NOTE: the transactions that our tools put have a single sender, and a single output address. This is assumed by this code. """ sender = None recipient = None recipient_address = None import_update_hash = None address = None sender_pubkey_hex = None if len(senders) == 0: raise Exception("No senders for (%s, %s)" % (opcode, hexlify(data))) # the first sender is always the first non-nulldata output script hex, and by construction # of Blockstore, this is always the principal that issued the operation. if 'script_pubkey' not in senders[0].keys(): raise Exception("No script_pubkey in sender of (%s, %s)" % (opcode, hexlify(data))) if 'addresses' not in senders[0].keys(): log.error("No addresses in sender of (%s, %s)" % (opcode, hexlify(data))) return None if len(senders[0]['addresses']) != 1: log.error("Multisig transactions are unsupported for (%s, %s)" % (opcode, hexlify(data))) return None sender = str(senders[0]['script_pubkey']) address = str(senders[0]['addresses'][0]) if str(senders[0]['script_type']) == 'pubkeyhash': sender_pubkey_hex = get_public_key_hex_from_tx( inputs, address ) if sender_pubkey_hex is None: log.warning("No public key found for (%s, %s)" % (opcode, hexlify(data))) op_fee = get_burn_fee_from_outputs( outputs ) if opcode in [NAME_REGISTRATION, NAMESPACE_REVEAL]: # these operations have a designated recipient that is *not* the sender try: recipient = get_registration_recipient_from_outputs( outputs ) recipient_address = pybitcoin.script_hex_to_address( recipient ) except Exception, e: log.exception(e) raise Exception("No registration address for (%s, %s)" % (opcode, hexlify(data)))
if opcode in [NAME_REGISTRATION, NAMESPACE_REVEAL]: # these operations have a designated recipient that is *not* the sender try: recipient = get_registration_recipient_from_outputs( outputs ) recipient_address = pybitcoin.script_hex_to_address( recipient ) except Exception, e: log.exception(e) raise Exception("No registration address for (%s, %s)" % (opcode, hexlify(data))) if opcode in [NAME_IMPORT, NAME_TRANSFER]: # these operations have a designated recipient that is *not* the sender try: recipient = get_transfer_recipient_from_outputs( outputs ) recipient_address = pybitcoin.script_hex_to_address( recipient ) except Exception, e: log.exception(e) raise Exception("No recipient for (%s, %s)" % (opcode, hexlify(data))) if opcode in [NAME_IMPORT]: # this operation has an update hash embedded as a phony recipient try: import_update_hash = get_import_update_hash_from_outputs( outputs, recipient ) except Exception, e: log.exception(e) raise Exception("No update hash for (%s, %s)" % (opcode, hexlify(data))) op = parse_blockstore_op_data(opcode, data, sender, recipient=recipient, recipient_address=recipient_address, import_update_hash=import_update_hash )
def db_parse(block_id, opcode, data, senders, inputs, outputs, fee, db_state=None): """ (required by virtualchain state engine) Parse a blockstore operation from a transaction's nulldata (data) and a list of outputs, as well as optionally the list of transaction's senders and the total fee paid. Return a parsed operation, and will also optionally have: * "sender": the first (primary) sender's script_pubkey. * "address": the sender's bitcoin address * "fee": the total fee paid for this record. * "recipient": the first non-OP_RETURN output's script_pubkey. * "sender_pubkey": the sender's public key (hex string), if this is a p2pkh transaction Return None on error NOTE: the transactions that our tools put have a single sender, and a single output address. This is assumed by this code. """ sender = None recipient = None recipient_address = None import_update_hash = None address = None sender_pubkey_hex = None if len(senders) == 0: raise Exception("No senders for (%s, %s)" % (opcode, hexlify(data))) # the first sender is always the first non-nulldata output script hex, and by construction # of Blockstore, this is always the principal that issued the operation. if 'script_pubkey' not in senders[0].keys(): raise Exception("No script_pubkey in sender of (%s, %s)" % (opcode, hexlify(data))) if 'addresses' not in senders[0].keys(): log.error("No addresses in sender of (%s, %s)" % (opcode, hexlify(data))) return None if len(senders[0]['addresses']) != 1: log.error("Multisig transactions are unsupported for (%s, %s)" % (opcode, hexlify(data))) return None sender = str(senders[0]['script_pubkey']) address = str(senders[0]['addresses'][0]) if str(senders[0]['script_type']) == 'pubkeyhash': sender_pubkey_hex = get_public_key_hex_from_tx(inputs, address) if sender_pubkey_hex is None: log.warning("No public key found for (%s, %s)" % (opcode, hexlify(data))) op_fee = get_burn_fee_from_outputs(outputs) if opcode in [NAME_REGISTRATION, NAMESPACE_REVEAL]: # these operations have a designated recipient that is *not* the sender try: recipient = get_registration_recipient_from_outputs(outputs) recipient_address = pybitcoin.script_hex_to_address(recipient) except Exception, e: log.exception(e) raise Exception("No registration address for (%s, %s)" % (opcode, hexlify(data)))
if opcode in [NAME_REGISTRATION, NAMESPACE_REVEAL]: # these operations have a designated recipient that is *not* the sender try: recipient = get_registration_recipient_from_outputs(outputs) recipient_address = pybitcoin.script_hex_to_address(recipient) except Exception, e: log.exception(e) raise Exception("No registration address for (%s, %s)" % (opcode, hexlify(data))) if opcode in [NAME_IMPORT, NAME_TRANSFER]: # these operations have a designated recipient that is *not* the sender try: recipient = get_transfer_recipient_from_outputs(outputs) recipient_address = pybitcoin.script_hex_to_address(recipient) except Exception, e: log.exception(e) raise Exception("No recipient for (%s, %s)" % (opcode, hexlify(data))) if opcode in [NAME_IMPORT]: # this operation has an update hash embedded as a phony recipient try: import_update_hash = get_import_update_hash_from_outputs( outputs, recipient) except Exception, e: log.exception(e) raise Exception("No update hash for (%s, %s)" % (opcode, hexlify(data)))