Stop adding more outputs than bulletproof allows

If more outputs are requested, they are split across
multiple transactions.

#7322
This commit is contained in:
Alex Opie 2021-01-22 11:38:18 +13:00 committed by selsta
parent 67ba733de1
commit 76824bf827
No known key found for this signature in database
GPG key ID: 2EA0A99A8B07AE5E

View file

@ -9768,13 +9768,18 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
TX() : weight(0), needed_fee(0) {} TX() : weight(0), needed_fee(0) {}
void add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations) { /* Add an output to the transaction.
* Returns True if the output was added, False if there are no more available output slots.
*/
bool add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations, size_t max_dsts) {
if (merge_destinations) if (merge_destinations)
{ {
std::vector<cryptonote::tx_destination_entry>::iterator i; std::vector<cryptonote::tx_destination_entry>::iterator i;
i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &de.addr, sizeof(de.addr)); }); i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &de.addr, sizeof(de.addr)); });
if (i == dsts.end()) if (i == dsts.end())
{ {
if (dsts.size() >= max_dsts)
return false;
dsts.push_back(de); dsts.push_back(de);
i = dsts.end() - 1; i = dsts.end() - 1;
i->amount = 0; i->amount = 0;
@ -9787,12 +9792,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size())); std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size()));
if (original_output_index == dsts.size()) if (original_output_index == dsts.size())
{ {
if (dsts.size() >= max_dsts)
return false;
dsts.push_back(de); dsts.push_back(de);
dsts.back().amount = 0; dsts.back().amount = 0;
} }
THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address"); THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address");
dsts[original_output_index].amount += amount; dsts[original_output_index].amount += amount;
} }
return true;
} }
}; };
std::vector<TX> txes; std::vector<TX> txes;
@ -10062,6 +10070,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// clear any fake outs we'd already gathered, since we'll need a new set // clear any fake outs we'd already gathered, since we'll need a new set
outs.clear(); outs.clear();
bool out_slots_exhausted = false;
if (adding_fee) if (adding_fee)
{ {
LOG_PRINT_L2("We need more fee, adding it to fee"); LOG_PRINT_L2("We need more fee, adding it to fee");
@ -10074,29 +10083,48 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// we can fully pay that destination // we can fully pay that destination
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(dsts[0].amount)); " for " << print_money(dsts[0].amount));
tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations); if (!tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1))
{
LOG_PRINT_L2("Didn't pay: ran out of output slots");
out_slots_exhausted = true;
break;
}
available_amount -= dsts[0].amount; available_amount -= dsts[0].amount;
dsts[0].amount = 0; dsts[0].amount = 0;
pop_index(dsts, 0); pop_index(dsts, 0);
++original_output_index; ++original_output_index;
} }
if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {
// we can partially fill that destination // we can partially fill that destination
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations); if (tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1))
{
dsts[0].amount -= available_amount; dsts[0].amount -= available_amount;
available_amount = 0; available_amount = 0;
} }
else
{
LOG_PRINT_L2("Didn't pay: ran out of output slots");
out_slots_exhausted = true;
}
}
} }
// here, check if we need to sent tx and start a new one // here, check if we need to sent tx and start a new one
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
<< upper_transaction_weight_limit); << upper_transaction_weight_limit);
bool try_tx = false; bool try_tx = false;
// If the new transaction is full, create it and start a new one
if (out_slots_exhausted)
{
LOG_PRINT_L2("Transaction is full, will create it and start a new tx");
try_tx = true;
}
// if we have preferred picks, but haven't yet used all of them, continue // if we have preferred picks, but haven't yet used all of them, continue
if (preferred_inputs.empty()) else if (preferred_inputs.empty())
{ {
if (adding_fee) if (adding_fee)
{ {