Beispiel #1
0
def sanitize_tx_data(unspents,
                     outputs,
                     fee,
                     leftover,
                     combine=True,
                     message=None,
                     compressed=True,
                     custom_pushdata=False):
    """
    sanitize_tx_data()

    fee is in satoshis per byte.
    """

    outputs = outputs.copy()

    for i, output in enumerate(outputs):
        dest, amount, currency = output
        # LEGACYADDRESSDEPRECATION
        # FIXME: Will be removed in an upcoming release, breaking compatibility with legacy addresses.
        #dest = cashaddress.to_cash_address(dest)
        outputs[i] = (dest, currency_to_satoshi_cached(amount, currency))

    if not unspents:
        raise ValueError('Transactions must have at least one unspent.')

    # Temporary storage so all outputs precede messages.
    messages = []
    total_op_return_size = 0

    if message and (custom_pushdata is False):
        try:
            message = message.encode('utf-8')
        except AttributeError:
            pass  # assume message is already a bytes-like object

        message_chunks = chunk_data(message, MESSAGE_LIMIT)

        for message in message_chunks:
            messages.append((message, 0))
            total_op_return_size += get_op_return_size(message,
                                                       custom_pushdata=False)

    elif message and (custom_pushdata is True):
        if (len(message) >= 220):
            # FIXME add capability for >220 bytes for custom pushdata elements
            raise ValueError(
                "Currently cannot exceed 220 bytes with custom_pushdata.")
        else:
            messages.append((message, 0))
            total_op_return_size += get_op_return_size(message,
                                                       custom_pushdata=True)

    # Include return address in fee estimate.
    total_in = 0
    num_outputs = len(outputs) + 1
    sum_outputs = sum(out[1] for out in outputs)

    if combine:
        # calculated_fee is in total satoshis.
        calculated_fee = estimate_tx_fee(len(unspents), num_outputs, fee,
                                         compressed, total_op_return_size)
        total_out = sum_outputs + calculated_fee
        unspents = unspents.copy()
        total_in += sum(unspent.amount for unspent in unspents)

    else:
        unspents = sorted(unspents, key=lambda x: x.amount)

        index = 0

        for index, unspent in enumerate(unspents):
            total_in += unspent.amount
            calculated_fee = estimate_tx_fee(len(unspents[:index + 1]),
                                             num_outputs, fee, compressed,
                                             total_op_return_size)
            total_out = sum_outputs + calculated_fee

            if total_in >= total_out:
                break

        unspents[:] = unspents[:index + 1]

    remaining = total_in - total_out

    if remaining > 0:
        outputs.append((leftover, remaining))
    elif remaining < 0:
        raise InsufficientFunds('Balance {} is less than {} (including '
                                'fee).'.format(total_in, total_out))

    outputs.extend(messages)

    return unspents, outputs
Beispiel #2
0
def sanitize_tx_data(unspents,
                     outputs,
                     fee,
                     leftover,
                     combine=True,
                     message=None,
                     compressed=True,
                     custom_pushdata=False):
    """
    sanitize_tx_data()

    fee is in satoshis per byte.
    """

    outputs = deque(outputs)

    for i, output in enumerate(outputs):
        dest, amount, currency = output
        outputs[i] = (dest, currency_to_satoshi_cached(amount, currency))

    if not unspents:
        raise ValueError('Transactions must have at least one unspent.')

    # Temporary storage so all outputs precede messages.
    messages = deque()
    total_op_return_size = 0

    if message and (custom_pushdata is False):
        try:
            message = message.encode('utf-8')
        except AttributeError:
            pass  # assume message is already a bytes-like object

        message_chunks = chunk_data(message, MESSAGE_LIMIT)

        for message in message_chunks:
            messages.appendleft((message, 0))
            total_op_return_size += get_op_return_size(message,
                                                       custom_pushdata=False)

    elif message and (custom_pushdata is True):
        if len(message) >= MESSAGE_LIMIT:
            raise ValueError(
                "Currently cannot exceed 100000 bytes with custom_pushdata.")
        else:
            messages.append((message, 0))
            total_op_return_size += get_op_return_size(message,
                                                       custom_pushdata=True)

    # Include return address in fee estimate.
    total_in = 0
    num_outputs = len(outputs) + 1
    sum_outputs = sum(out[1] for out in outputs)

    if combine:
        # calculated_fee is in total satoshis.
        calculated_fee = estimate_tx_fee(len(unspents), num_outputs, fee,
                                         compressed, total_op_return_size)
        total_out = sum_outputs + calculated_fee
        unspents = unspents.copy()
        total_in += sum(unspent.amount for unspent in unspents)

    else:
        unspents = sorted(unspents, key=lambda x: x.amount)

        index = 0

        for index, unspent in enumerate(unspents):
            total_in += unspent.amount
            calculated_fee = estimate_tx_fee(len(unspents[:index + 1]),
                                             num_outputs, fee, compressed,
                                             total_op_return_size)
            total_out = sum_outputs + calculated_fee

            if total_in >= total_out:
                break

        unspents[:] = unspents[:index + 1]

    remaining = total_in - total_out

    # If the uxto less than dust (546) the miner will not relay that tx, even the service can successful return.
    # Here we put all the remnant (<546) to the miner in this case.
    # We could adjust here when new dust agreement reached in future.
    if remaining > DUST:
        outputs.append((leftover, remaining))
    elif remaining < 0:
        raise InsufficientFunds('Balance {} is less than {} (including '
                                'fee).'.format(total_in, total_out))

    outputs.extendleft(messages)

    return unspents, list(outputs)
Beispiel #3
0
def test_chunk_data():
    assert list(chunk_data(ODD_HEX, 2)) == [
        '4f', 'ad', 'd1', '97', '73', '28', 'c1', '1e', 'fc', '1c', '1d', '8a',
        '78', '1a', 'a6', 'b9', '67', '79', '84', 'd3', 'e0', 'bd', '0b', 'fc',
        '52', 'b9', 'f3', 'b0', '38', '85', 'a0', '0'
    ]