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", ]
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 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(f"Balance {total_in} is less than " f"{total_out} (including fee).") outputs.extend(messages) return unspents, outputs
def sanitize_tx_data(unspents, outputs, fee, leftover, combine=True, message=None, compressed=True): outputs = outputs.copy() 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 = [] if message: message_chunks = chunk_data(message.encode('utf-8'), MESSAGE_LIMIT) for message in message_chunks: messages.append((message, 0)) # Include return address in fee estimate. fee = estimate_tx_fee(len(unspents), len(outputs) + len(messages) + 1, fee, compressed) total_out = sum(out[1] for out in outputs) + fee total_in = 0 if combine: 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 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): """ 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 = [] if message: message_chunks = chunk_data(message.encode('utf-8'), MESSAGE_LIMIT) for message in message_chunks: messages.append((message, 0)) # Include return address in fee estimate. total_in = 0 num_outputs = len(outputs) + len(messages) + 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_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_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 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' ]