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
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)
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' ]