mirror of https://github.com/oxen-io/oxen-core.git
114 lines
5.0 KiB
C++
114 lines
5.0 KiB
C++
#include "transaction_constructor.hpp"
|
|
#include "pending_transaction.hpp"
|
|
#include "decoy.hpp"
|
|
#include "output_selection/output_selection.hpp"
|
|
#include "decoy_selection/decoy_selection.hpp"
|
|
#include "db_schema.hpp"
|
|
|
|
#include <cryptonote_basic/hardfork.h>
|
|
|
|
//TODO: nettype-based tx construction parameters
|
|
|
|
namespace wallet
|
|
{
|
|
// create_transaction will create a vanilla spend transaction without any special features.
|
|
PendingTransaction
|
|
TransactionConstructor::create_transaction(
|
|
const std::vector<cryptonote::tx_destination_entry>& recipients,
|
|
const cryptonote::tx_destination_entry& change_recipient)
|
|
{
|
|
PendingTransaction new_tx(recipients);
|
|
auto [hf, hf_uint8] = cryptonote::get_ideal_block_version(db->network_type(), db->scan_target_height());
|
|
cryptonote::oxen_construct_tx_params tx_params{hf, cryptonote::txtype::standard, 0, 0};
|
|
new_tx.tx.version = cryptonote::transaction::get_max_version_for_hf(tx_params.hf_version);
|
|
new_tx.tx.type = tx_params.tx_type;
|
|
new_tx.fee_per_byte = fee_per_byte;
|
|
new_tx.fee_per_output = fee_per_output;
|
|
new_tx.change = change_recipient;
|
|
select_inputs_and_finalise(new_tx);
|
|
return new_tx;
|
|
}
|
|
|
|
|
|
// SelectInputs will choose some available unspent outputs from the database and allocate to the
|
|
// transaction can be called multiple times and will add until enough is sufficient
|
|
void
|
|
TransactionConstructor::select_inputs(PendingTransaction& ptx) const
|
|
{
|
|
const int64_t single_input_size = ptx.get_fee(1);
|
|
const int64_t double_input_size = ptx.get_fee(2);
|
|
const int64_t additional_input = double_input_size - single_input_size;
|
|
const int64_t dust_amount = single_input_size * ptx.fee_per_byte;
|
|
|
|
OutputSelector select_outputs{};
|
|
const int noutputs_estimate = 300; // number of outputs to precompute fee for
|
|
for (int64_t output_count = 1; output_count < noutputs_estimate; ++output_count)
|
|
{
|
|
select_outputs.push_fee(output_count, ptx.get_fee(output_count));
|
|
}
|
|
int64_t transaction_total = ptx.sum_outputs();
|
|
|
|
// Check that we actually have enough in the outputs to build this transaction. Fail early. We
|
|
// then increase the transaction_total to include an amount sufficient to cover a reasonable
|
|
// change amount. Transaction fee is high for the first input because there is overhead to cover
|
|
// and prefer that the change amount is enough to cover that overhead, but if we dont have enough
|
|
// in the wallet then try to ensure there is enough to cover the fee
|
|
// as an additional (2nd+) input. Finally if the wallet balance is not sufficient
|
|
// allow the change to be dust but this will only occur if the wallet has enough to cover the
|
|
// transaction but not enough to also cover the dust which should be extremely unlikely.
|
|
int64_t wallet_balance = db->available_balance(additional_input * static_cast<int64_t>(ptx.fee_per_byte));
|
|
if (wallet_balance < transaction_total)
|
|
throw std::runtime_error("Insufficient Wallet Balance");
|
|
else if (wallet_balance > transaction_total + single_input_size * static_cast<int64_t>(ptx.fee_per_byte))
|
|
transaction_total += single_input_size * ptx.fee_per_byte;
|
|
else if (wallet_balance > transaction_total + additional_input * static_cast<int64_t>(ptx.fee_per_byte))
|
|
transaction_total += additional_input * ptx.fee_per_byte;
|
|
|
|
// Selects all outputs where the amount is greater than the estimated fee for an ADDITIONAL input.
|
|
auto available_outputs = db->available_outputs(additional_input * static_cast<int64_t>(ptx.fee_per_byte));
|
|
ptx.chosen_outputs = select_outputs(available_outputs, ptx.sum_outputs());
|
|
ptx.fee = ptx.get_fee();
|
|
ptx.update_change();
|
|
}
|
|
|
|
// select_and_fetch_decoys will choose some available outputs from the database, fetch the
|
|
// details necessary for a ring signature from the daemon and add them to the
|
|
// transaction ready to sign at a later point in time.
|
|
void
|
|
TransactionConstructor::select_and_fetch_decoys(PendingTransaction& ptx)
|
|
{
|
|
ptx.decoys = {};
|
|
// This initialises the decoys to be selected from global_output_index= 0 to global_output_index = highest_output_index
|
|
int64_t max_output_index = db->chain_output_count();
|
|
//DecoySelector decoy_selection(0, max_output_index);
|
|
DecoySelector& decoy_selection = *decoy_selector;
|
|
std::vector<int64_t> indexes;
|
|
for (const auto& output : ptx.chosen_outputs)
|
|
{
|
|
indexes = decoy_selection(output);
|
|
auto decoy_future = daemon->fetch_decoys(indexes);
|
|
decoy_future.wait();
|
|
ptx.decoys.emplace_back(decoy_future.get());
|
|
|
|
bool good = false;
|
|
for(const auto& decoy : ptx.decoys.back())
|
|
good |= (output.key == decoy.key);
|
|
if (!good)
|
|
throw std::runtime_error{"Key from daemon for real output does not match our stored key."};
|
|
}
|
|
}
|
|
|
|
void
|
|
TransactionConstructor::select_inputs_and_finalise(PendingTransaction& ptx)
|
|
{
|
|
while (true)
|
|
{
|
|
if (ptx.finalise())
|
|
break;
|
|
else
|
|
select_inputs(ptx);
|
|
}
|
|
select_and_fetch_decoys(ptx);
|
|
}
|
|
} // namespace wallet
|