Example #1
0
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