def insert_raw_transaction(raw_transaction, db, rawtransactions_db): """Add a raw transaction to the database.""" # one transaction per block block_index, block_hash, block_time = create_next_block(db) cursor = db.cursor() tx_index = block_index - config.BURN_START + 1 tx_hash = hashlib.sha256('{}{}'.format( tx_index, raw_transaction).encode('utf-8')).hexdigest() # print(tx_hash) # Remember to add it to the log dump if pytest.config.option.savescenarios: save_rawtransaction(rawtransactions_db, tx_hash, raw_transaction) source, destination, btc_amount, fee, data = blocks.get_tx_info2( raw_transaction) transaction = (tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data, True) cursor.execute( '''INSERT INTO transactions VALUES (?,?,?,?,?,?,?,?,?,?,?)''', transaction) tx = list( cursor.execute('''SELECT * FROM transactions WHERE tx_index = ?''', (tx_index, )))[0] cursor.close() util.CURRENT_BLOCK_INDEX = block_index # TODO: Correct?! blocks.parse_block(db, block_index, block_time) return tx
def insert_raw_transaction(raw_transaction, db, rawtransactions_db): """Add a raw transaction to the database.""" # one transaction per block block_index, block_hash, block_time = create_next_block(db) cursor = db.cursor() tx_index = block_index - config.BURN_START + 1 tx_hash = hashlib.sha256('{}{}'.format(tx_index,raw_transaction).encode('utf-8')).hexdigest() # print(tx_hash) # Remember to add it to the log dump if pytest.config.option.savescenarios: save_rawtransaction(rawtransactions_db, tx_hash, raw_transaction) source, destination, btc_amount, fee, data = blocks.get_tx_info2(raw_transaction) transaction = (tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data, True) cursor.execute('''INSERT INTO transactions VALUES (?,?,?,?,?,?,?,?,?,?,?)''', transaction) tx = list(cursor.execute('''SELECT * FROM transactions WHERE tx_index = ?''', (tx_index,)))[0] cursor.close() util.CURRENT_BLOCK_INDEX = block_index # TODO: Correct?! blocks.parse_block(db, block_index, block_time) return tx
def construct (db, tx_info, encoding='auto', fee_per_kb=config.DEFAULT_FEE_PER_KB, regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE, multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE, op_return_value=config.DEFAULT_OP_RETURN_VALUE, exact_fee=None, fee_provided=0, provided_pubkeys=None, allow_unconfirmed_inputs=False): (source, destination_outputs, data) = tx_info # Sanity checks. if exact_fee and not isinstance(exact_fee, int): raise exceptions.TransactionError('Exact fees must be in satoshis.') if not isinstance(fee_provided, int): raise exceptions.TransactionError('Fee provided must be in satoshis.') '''Destinations''' # Destination outputs. # Replace multi‐sig addresses with multi‐sig pubkeys. Check that the # destination output isn’t a dust output. Set null values to dust size. destination_outputs_new = [] for (address, value) in destination_outputs: # Value. if script.is_multisig(address): dust_size = multisig_dust_size else: dust_size = regular_dust_size if value == None: value = dust_size elif value < dust_size: raise exceptions.TransactionError('Destination output is dust.') # Address. script.validate(address) if script.is_multisig(address): destination_outputs_new.append((backend.multisig_pubkeyhashes_to_pubkeys(address, provided_pubkeys), value)) else: destination_outputs_new.append((address, value)) destination_outputs = destination_outputs_new destination_btc_out = sum([value for address, value in destination_outputs]) '''Data''' # Data encoding methods (choose and validate). if data: if encoding == 'auto': if len(data) + len(config.PREFIX) <= config.OP_RETURN_MAX_SIZE: encoding = 'opreturn' else: encoding = 'multisig' elif encoding not in ('pubkeyhash', 'multisig', 'opreturn'): raise exceptions.TransactionError('Unknown encoding‐scheme.') # Divide data into chunks. if data: if encoding == 'pubkeyhash': # Prefix is also a suffix here. chunk_size = 20 - 1 - 8 elif encoding == 'multisig': # Two pubkeys, minus length byte, minus prefix, minus two nonces, # minus two sign bytes. chunk_size = (33 * 2) - 1 - 8 - 2 - 2 elif encoding == 'opreturn': chunk_size = config.OP_RETURN_MAX_SIZE if len(data) + len(config.PREFIX) > chunk_size: raise exceptions.TransactionError('One `OP_RETURN` output per transaction.') data_array = list(chunks(data, chunk_size)) else: data_array = [] # Data outputs. if data: if encoding == 'multisig': data_value = multisig_dust_size elif encoding == 'opreturn': data_value = op_return_value else: # Pay‐to‐PubKeyHash, e.g. data_value = regular_dust_size data_output = (data_array, data_value) else: data_output = None data_btc_out = sum([data_value for data_chunk in data_array]) '''Inputs''' # Source. # If public key is necessary for construction of (unsigned) # transaction, use the public key provided, or find it from the # blockchain. if source: script.validate(source) if encoding == 'multisig': dust_return_pubkey = get_dust_return_pubkey(source, provided_pubkeys, encoding) else: dust_return_pubkey = None # Calculate collective size of outputs, for fee calculation. if encoding == 'multisig': data_output_size = 81 # 71 for the data elif encoding == 'opreturn': data_output_size = 90 # 80 for the data else: data_output_size = 25 + 9 # Pay‐to‐PubKeyHash (25 for the data?) outputs_size = ((25 + 9) * len(destination_outputs)) + (len(data_array) * data_output_size) # Get inputs. multisig_inputs = not data unspent = backend.get_unspent_txouts(source, unconfirmed=allow_unconfirmed_inputs, multisig_inputs=multisig_inputs) unspent = backend.sort_unspent_txouts(unspent) logger.debug('Sorted UTXOs: {}'.format([print_coin(coin) for coin in unspent])) inputs = [] btc_in = 0 change_quantity = 0 sufficient_funds = False final_fee = fee_per_kb for coin in unspent: logger.debug('New input: {}'.format(print_coin(coin))) inputs.append(coin) btc_in += round(coin['amount'] * config.UNIT) # If exact fee is specified, use that. Otherwise, calculate size of tx # and base fee on that (plus provide a minimum fee for selling BTC). if exact_fee: final_fee = exact_fee else: size = 181 * len(inputs) + outputs_size + 10 necessary_fee = (int(size / 1000) + 1) * fee_per_kb final_fee = max(fee_provided, necessary_fee) assert final_fee >= 1 * fee_per_kb # Check if good. btc_out = destination_btc_out + data_btc_out change_quantity = btc_in - (btc_out + final_fee) logger.debug('Change quantity: {} BTC'.format(change_quantity / config.UNIT)) # If change is necessary, must not be a dust output. if change_quantity == 0 or change_quantity >= regular_dust_size: sufficient_funds = True break if not sufficient_funds: # Approximate needed change, fee by with most recently calculated # quantities. btc_out = destination_btc_out + data_btc_out total_btc_out = btc_out + max(change_quantity, 0) + final_fee raise exceptions.BalanceError('Insufficient {} at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`. (Unconfirmed coins cannot be spent from multi‐sig addresses.)'.format(config.BTC, source, total_btc_out / config.UNIT, config.BTC)) '''Finish''' # Change output. if change_quantity: if script.is_multisig(source): change_address = backend.multisig_pubkeyhashes_to_pubkeys(source, provided_pubkeys) else: change_address = source change_output = (change_address, change_quantity) else: change_output = None # Serialise inputs and outputs. unsigned_tx = serialise(encoding, inputs, destination_outputs, data_output, change_output, dust_return_pubkey=dust_return_pubkey) unsigned_tx_hex = binascii.hexlify(unsigned_tx).decode('utf-8') '''Sanity Check''' from counterpartylib.lib import blocks # Desired transaction info. (desired_source, desired_destination_outputs, desired_data) = tx_info desired_source = script.make_canonical(desired_source) desired_destination = script.make_canonical(desired_destination_outputs[0][0]) if desired_destination_outputs else '' # NOTE: Include change in destinations for BTC transactions. # if change_output and not desired_data and desired_destination != config.UNSPENDABLE: # if desired_destination == '': # desired_destination = desired_source # else: # desired_destination += '-{}'.format(desired_source) # NOTE if desired_data == None: desired_data = b'' # Parsed transaction info. try: parsed_source, parsed_destination, x, y, parsed_data = blocks.get_tx_info2(unsigned_tx_hex) except exceptions.BTCOnlyError: # Skip BTC‐only transactions. return unsigned_tx_hex desired_source = script.make_canonical(desired_source) # Check desired info against parsed info. desired = (desired_source, desired_destination, desired_data) parsed = (parsed_source, parsed_destination, parsed_data) if desired != parsed: raise exceptions.TransactionError('Constructed transaction does not parse correctly: {} ≠ {}'.format(desired, parsed)) return unsigned_tx_hex
def construct(db, tx_info, encoding='auto', fee_per_kb=config.DEFAULT_FEE_PER_KB, regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE, multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE, op_return_value=config.DEFAULT_OP_RETURN_VALUE, exact_fee=None, fee_provided=0, provided_pubkeys=None, allow_unconfirmed_inputs=False): (source, destination_outputs, data) = tx_info # Sanity checks. if exact_fee and not isinstance(exact_fee, int): raise exceptions.TransactionError('Exact fees must be in satoshis.') if not isinstance(fee_provided, int): raise exceptions.TransactionError('Fee provided must be in satoshis.') '''Destinations''' # Destination outputs. # Replace multi‐sig addresses with multi‐sig pubkeys. Check that the # destination output isn’t a dust output. Set null values to dust size. destination_outputs_new = [] for (address, value) in destination_outputs: # Value. if script.is_multisig(address): dust_size = multisig_dust_size else: dust_size = regular_dust_size if value == None: value = dust_size elif value < dust_size: raise exceptions.TransactionError('Destination output is dust.') # Address. script.validate(address) if script.is_multisig(address): destination_outputs_new.append( (backend.multisig_pubkeyhashes_to_pubkeys( address, provided_pubkeys), value)) else: destination_outputs_new.append((address, value)) destination_outputs = destination_outputs_new destination_btc_out = sum( [value for address, value in destination_outputs]) '''Data''' # Data encoding methods (choose and validate). if data: if encoding == 'auto': if len(data) <= config.OP_RETURN_MAX_SIZE: encoding = 'multisig' # BTCGuild isn’t mining `OP_RETURN`?! else: encoding = 'multisig' elif encoding not in ('pubkeyhash', 'multisig', 'opreturn'): raise exceptions.TransactionError('Unknown encoding‐scheme.') # Divide data into chunks. if data: if encoding == 'pubkeyhash': # Prefix is also a suffix here. chunk_size = 20 - 1 - 8 elif encoding == 'multisig': # Two pubkeys, minus length byte, minus prefix, minus two nonces, # minus two sign bytes. chunk_size = (33 * 2) - 1 - 8 - 2 - 2 elif encoding == 'opreturn': chunk_size = config.OP_RETURN_MAX_SIZE if len(data) > chunk_size: raise exceptions.TransactionError( 'One `OP_RETURN` output per transaction.') data_array = list(chunks(data, chunk_size)) else: data_array = [] # Data outputs. if data: if encoding == 'multisig': data_value = multisig_dust_size elif encoding == 'opreturn': data_value = op_return_value else: # Pay‐to‐PubKeyHash, e.g. data_value = regular_dust_size data_output = (data_array, data_value) else: data_output = None data_btc_out = sum([data_value for data_chunk in data_array]) '''Inputs''' # Source. # If public key is necessary for construction of (unsigned) # transaction, use the public key provided, or find it from the # blockchain. if source: script.validate(source) dust_return_pubkey = get_dust_return_pubkey(source, provided_pubkeys, encoding) # Calculate collective size of outputs, for fee calculation. if encoding == 'multisig': data_output_size = 81 # 71 for the data elif encoding == 'opreturn': data_output_size = 90 # 80 for the data else: data_output_size = 25 + 9 # Pay‐to‐PubKeyHash (25 for the data?) outputs_size = ((25 + 9) * len(destination_outputs)) + (len(data_array) * data_output_size) # Get inputs. unspent = backend.get_unspent_txouts(source) unspent = backend.sort_unspent_txouts(unspent, allow_unconfirmed_inputs) logger.debug('Sorted UTXOs: {}'.format( [print_coin(coin) for coin in unspent])) inputs = [] btc_in = 0 change_quantity = 0 sufficient_funds = False final_fee = fee_per_kb for coin in unspent: logger.debug('New input: {}'.format(print_coin(coin))) inputs.append(coin) btc_in += round(coin['amount'] * config.UNIT) # If exact fee is specified, use that. Otherwise, calculate size of tx # and base fee on that (plus provide a minimum fee for selling BTC). if exact_fee: final_fee = exact_fee else: size = 181 * len(inputs) + outputs_size + 10 necessary_fee = (int(size / 1000) + 1) * fee_per_kb final_fee = max(fee_provided, necessary_fee) assert final_fee >= 1 * fee_per_kb # Check if good. btc_out = destination_btc_out + data_btc_out change_quantity = btc_in - (btc_out + final_fee) logger.debug('Change quantity: {} BTC'.format(change_quantity / config.UNIT)) # If change is necessary, must not be a dust output. if change_quantity == 0 or change_quantity >= regular_dust_size: sufficient_funds = True break if not sufficient_funds: # Approximate needed change, fee by with most recently calculated # quantities. btc_out = destination_btc_out + data_btc_out total_btc_out = btc_out + max(change_quantity, 0) + final_fee raise exceptions.BalanceError( 'Insufficient {} at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`. (Unconfirmed coins cannot be spent from multi‐sig addresses.)' .format(config.BTC, source, total_btc_out / config.UNIT, config.BTC)) '''Finish''' # Change output. if change_quantity: if script.is_multisig(source): change_address = backend.multisig_pubkeyhashes_to_pubkeys( source, provided_pubkeys) else: change_address = source change_output = (change_address, change_quantity) else: change_output = None # Serialise inputs and outputs. unsigned_tx = serialise(encoding, inputs, destination_outputs, data_output, change_output, dust_return_pubkey=dust_return_pubkey) unsigned_tx_hex = binascii.hexlify(unsigned_tx).decode('utf-8') '''Sanity Check''' from counterpartylib.lib import blocks # Desired transaction info. (desired_source, desired_destination_outputs, desired_data) = tx_info desired_source = script.make_canonical(desired_source) desired_destination = script.make_canonical( desired_destination_outputs[0] [0]) if desired_destination_outputs else '' # NOTE: Include change in destinations for BTC transactions. # if change_output and not desired_data and desired_destination != config.UNSPENDABLE: # if desired_destination == '': # desired_destination = desired_source # else: # desired_destination += '-{}'.format(desired_source) # NOTE if desired_data == None: desired_data = b'' # Parsed transaction info. try: parsed_source, parsed_destination, x, y, parsed_data = blocks.get_tx_info2( unsigned_tx_hex) except exceptions.BTCOnlyError: # Skip BTC‐only transactions. return unsigned_tx_hex desired_source = script.make_canonical(desired_source) # Check desired info against parsed info. if (desired_source, desired_destination, desired_data) != (parsed_source, parsed_destination, parsed_data): raise exceptions.TransactionError( 'constructed transaction does not parse correctly') return unsigned_tx_hex