mirror of https://github.com/oxen-io/oxen-core.git
Calculate the fee based on the current pending transaction.
When building the pending transaction we can call GetFee() to calculate how much the transaction will cost. It takes a single parameter for the number of inputs because we will want to specify how many when estimating. We then build a list of the potential fees for up to 300 inputs and pass that to our output selection function which will use it to determine if the outputs selected will be sufficient to cover the fees. This allows us to know in advance how much the fees will be rather than trial and error.
This commit is contained in:
parent
b85c01ba60
commit
0ac65075eb
|
@ -35,6 +35,9 @@ namespace wallet
|
|||
|
||||
virtual void
|
||||
deregister_wallet(Wallet& wallet, std::promise<void>& p) = 0;
|
||||
|
||||
virtual std::pair<int64_t, int64_t>
|
||||
get_fee_parameters() = 0;
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -175,6 +175,30 @@ namespace wallet
|
|||
// RPC response is chain length, not top height
|
||||
top_block_height = new_height - 1;
|
||||
}, "de");
|
||||
|
||||
omq->request(conn, "rpc.get_fee_estimate",
|
||||
[this](bool ok, std::vector<std::string> response)
|
||||
{
|
||||
if (not ok or response.size() != 2 or response[0] != "200")
|
||||
return;
|
||||
|
||||
oxenmq::bt_dict_consumer dc{response[1]};
|
||||
|
||||
int64_t new_fee_per_byte = 0;
|
||||
int64_t new_fee_per_output = 0;
|
||||
|
||||
if (not dc.skip_until("fee_per_byte"))
|
||||
throw std::runtime_error("bad response from rpc.get_fee_estimate, key 'fee_per_byte' missing");
|
||||
new_fee_per_byte = dc.consume_integer<int64_t>();
|
||||
|
||||
if (not dc.skip_until("fee_per_output"))
|
||||
throw std::runtime_error("bad response from rpc.get_fee_estimate, key 'fee_per_output' missing");
|
||||
new_fee_per_output = dc.consume_integer<int64_t>();
|
||||
|
||||
fee_per_byte = new_fee_per_byte;
|
||||
fee_per_output = new_fee_per_output;
|
||||
|
||||
}, "de");
|
||||
}
|
||||
|
||||
DefaultDaemonComms::DefaultDaemonComms(std::shared_ptr<oxenmq::OxenMQ> omq)
|
||||
|
@ -247,6 +271,12 @@ namespace wallet
|
|||
}, sync_thread);
|
||||
}
|
||||
|
||||
std::pair<int64_t, int64_t>
|
||||
DefaultDaemonComms::get_fee_parameters()
|
||||
{
|
||||
return std::make_pair(fee_per_byte,fee_per_output);
|
||||
}
|
||||
|
||||
void
|
||||
DefaultDaemonComms::deregister_wallet(wallet::Wallet& wallet, std::promise<void>& p)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "daemon_comms.hpp"
|
||||
#include "cryptonote_config.h"
|
||||
|
||||
#include <crypto/crypto.h>
|
||||
|
||||
|
@ -43,6 +44,10 @@ namespace wallet
|
|||
void
|
||||
deregister_wallet(Wallet& wallet, std::promise<void>& p);
|
||||
|
||||
|
||||
std::pair<int64_t, int64_t>
|
||||
get_fee_parameters();
|
||||
|
||||
private:
|
||||
|
||||
void
|
||||
|
@ -73,6 +78,9 @@ namespace wallet
|
|||
int64_t sync_from_height = 0;
|
||||
bool syncing = false;
|
||||
int64_t max_sync_blocks = DEFAULT_MAX_SYNC_BLOCKS;
|
||||
|
||||
int64_t fee_per_byte = FEE_PER_BYTE_V13;
|
||||
int64_t fee_per_output = FEE_PER_OUTPUT_V18;
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -11,7 +11,14 @@ namespace wallet
|
|||
available_outputs.end(),
|
||||
0,
|
||||
[](const auto& accumulator, const auto& x) { return accumulator + x.amount; });
|
||||
if (wallet_balance < amount)
|
||||
int64_t fee = 0;
|
||||
auto pos = fee_map.find(1);
|
||||
if (pos == fee_map.end()) {
|
||||
throw std::runtime_error("Missing fee amount");
|
||||
} else {
|
||||
fee = pos->second;
|
||||
}
|
||||
if (wallet_balance < amount + fee)
|
||||
throw std::runtime_error("Insufficient Wallet Balance");
|
||||
|
||||
// Prefer a single output if suitable
|
||||
|
@ -20,7 +27,7 @@ namespace wallet
|
|||
available_outputs.begin(),
|
||||
available_outputs.end(),
|
||||
std::back_inserter(outputs_bigger_than_amount),
|
||||
[amount](const auto& x) { return static_cast<int64_t>(x.amount) > amount; });
|
||||
[amount, fee](const auto& x) { return static_cast<int64_t>(x.amount) > amount + fee; });
|
||||
|
||||
if (outputs_bigger_than_amount.size() > 0)
|
||||
{
|
||||
|
@ -73,8 +80,14 @@ namespace wallet
|
|||
// Iterate through the list until we have sufficient return value
|
||||
std::vector<Output> multiple_outputs{};
|
||||
int i = 0;
|
||||
while (amount > 0)
|
||||
while (amount + fee > 0)
|
||||
{
|
||||
auto pos = fee_map.find(i+1);
|
||||
if (pos == fee_map.end()) {
|
||||
throw std::runtime_error("Missing fee amount");
|
||||
} else {
|
||||
fee = pos->second;
|
||||
}
|
||||
multiple_outputs.push_back(available_outputs[indices[i]]);
|
||||
amount = amount - available_outputs[indices[i]].amount;
|
||||
i++;
|
||||
|
|
|
@ -14,5 +14,17 @@ namespace wallet
|
|||
public:
|
||||
std::vector<Output>
|
||||
operator()(const std::vector<Output>& available_outputs, int64_t amount) const;
|
||||
|
||||
void
|
||||
push_fee(int64_t input_count, int64_t fee) {fee_map[input_count]=fee;};
|
||||
|
||||
void
|
||||
clear_fees(){ fee_map.clear(); };
|
||||
|
||||
private:
|
||||
// Keeps track of the fees that need to be paid on top of the amount passed in
|
||||
// key represents the number of outputs and value represents the fee that needs
|
||||
// to be included if that many outputs are chosen
|
||||
std::map<int64_t, int64_t> fee_map;
|
||||
};
|
||||
} // namespace wallet
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "transaction_constructor.hpp"
|
||||
#include "pending_transaction.hpp"
|
||||
#include "oxen_economy.h"
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
|
@ -20,23 +21,23 @@ namespace wallet
|
|||
}
|
||||
|
||||
void
|
||||
PendingTransaction::UpdateChange()
|
||||
PendingTransaction::update_change()
|
||||
{
|
||||
change.amount = SumInputs() - SumOutputs();
|
||||
change.amount = sum_inputs() - sum_outputs() - get_fee();
|
||||
}
|
||||
|
||||
int64_t
|
||||
PendingTransaction::SumInputs()
|
||||
PendingTransaction::sum_inputs() const
|
||||
{
|
||||
return std::accumulate(
|
||||
chosenOutputs.begin(),
|
||||
chosenOutputs.end(),
|
||||
chosen_outputs.begin(),
|
||||
chosen_outputs.end(),
|
||||
0,
|
||||
[](int64_t accumulator, const Output& output) { return accumulator + output.amount; });
|
||||
}
|
||||
|
||||
int64_t
|
||||
PendingTransaction::SumOutputs()
|
||||
PendingTransaction::sum_outputs() const
|
||||
{
|
||||
return std::accumulate(
|
||||
recipients.begin(),
|
||||
|
@ -47,10 +48,75 @@ namespace wallet
|
|||
});
|
||||
}
|
||||
|
||||
bool
|
||||
PendingTransaction::Finalise()
|
||||
int64_t
|
||||
PendingTransaction::get_fee() const
|
||||
{
|
||||
if (SumInputs() - SumOutputs() - change.amount == 0)
|
||||
return get_fee(chosen_outputs.size());
|
||||
}
|
||||
int64_t
|
||||
PendingTransaction::get_fee(int64_t n_inputs) const
|
||||
{
|
||||
// TODO sean add this
|
||||
int64_t fixed_fee = 0;
|
||||
// TODO sean add this
|
||||
int64_t burn_pct = 0;
|
||||
int64_t fee_percent = BLINK_BURN_TX_FEE_PERCENT_V18; // 100%
|
||||
if (blink)
|
||||
fee_percent = BLINK_MINER_TX_FEE_PERCENT + burn_pct; // Blink ends up being 300%
|
||||
|
||||
int64_t fee = (get_tx_weight(n_inputs) * fee_per_byte + (recipients.size() + 1) * fee_per_output) * fee_percent / 100;
|
||||
// Add fixed amount to the fee for items such as burning. This is defined in the pending transactions
|
||||
fee += fixed_fee;
|
||||
return fee;
|
||||
}
|
||||
|
||||
size_t
|
||||
PendingTransaction::get_tx_weight(int64_t n_inputs) const
|
||||
{
|
||||
size_t size = 0;
|
||||
// If there is no inputs then we estimate using one input
|
||||
if (n_inputs == 0)
|
||||
n_inputs = 1;
|
||||
|
||||
size_t n_outputs = recipients.size() + 1; // Recipients plus change
|
||||
if (n_outputs == 0)
|
||||
throw std::runtime_error{"Get Transaction Weight called on a transaction with no recipients"};
|
||||
|
||||
size += 1 + 6; // tx prefix, first few bytes
|
||||
size += n_inputs * (1+6+(mixin_count+1)*2+32); // vin
|
||||
size += n_outputs * (6+32); // vout
|
||||
size += extra_size(); // extra
|
||||
// rct signatures
|
||||
size += 1; // type
|
||||
size_t log_padded_outputs = 0;
|
||||
while ((uint64_t(1)<<log_padded_outputs) < n_outputs)
|
||||
++log_padded_outputs;
|
||||
size += (2 * (6 + static_cast<int64_t>(log_padded_outputs)) + 4 + 5) * 32 + 3; // rangeSigs
|
||||
|
||||
size += n_inputs * (32 * (mixin_count+1) + 64); // CLSAGs
|
||||
size += 32 * n_inputs; // pseudoOuts
|
||||
size += 8 * n_outputs; // ecdhInfo
|
||||
size += 32 * n_outputs; // outPk - only commitment is saved
|
||||
size += 4; // txnFee
|
||||
|
||||
if (n_outputs > 2)
|
||||
{
|
||||
const uint64_t bp_base = 368;
|
||||
size_t log_padded_outputs = 2;
|
||||
while ((uint64_t(1)<<log_padded_outputs) < n_outputs)
|
||||
++log_padded_outputs;
|
||||
uint64_t nlr = 2 * (6 + log_padded_outputs);
|
||||
const uint64_t bp_size = 32 * (9 + nlr);
|
||||
const uint64_t bp_clawback = (bp_base * (1<<log_padded_outputs) - bp_size) * 4 / 5;
|
||||
size += bp_clawback;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
bool
|
||||
PendingTransaction::finalise()
|
||||
{
|
||||
if (sum_inputs() - sum_outputs() - fee - change.amount == 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace wallet
|
|||
|
||||
struct PendingTransaction
|
||||
{
|
||||
version txVersion;
|
||||
version tx_version;
|
||||
|
||||
std::vector<TransactionRecipient> recipients; // does not include change
|
||||
|
||||
|
@ -33,23 +33,39 @@ namespace wallet
|
|||
|
||||
cryptonote::transaction tx;
|
||||
|
||||
std::vector<Output> chosenOutputs;
|
||||
std::vector<Output> chosen_outputs;
|
||||
|
||||
bool blink = true;
|
||||
|
||||
int64_t fee = 0;
|
||||
uint64_t fee_per_byte = FEE_PER_BYTE_V13;
|
||||
uint64_t fee_per_output = FEE_PER_OUTPUT_V18;
|
||||
int8_t mixin_count = CRYPTONOTE_DEFAULT_TX_MIXIN;
|
||||
size_t extra_size() const {return 0;};
|
||||
|
||||
PendingTransaction() = default;
|
||||
|
||||
PendingTransaction(const std::vector<TransactionRecipient>& new_recipients);
|
||||
|
||||
int64_t
|
||||
get_fee() const;
|
||||
int64_t
|
||||
get_fee(int64_t n_inputs) const;
|
||||
|
||||
size_t
|
||||
get_tx_weight(int64_t n_inputs) const;
|
||||
|
||||
void
|
||||
UpdateChange();
|
||||
update_change();
|
||||
|
||||
int64_t
|
||||
SumInputs();
|
||||
sum_inputs() const;
|
||||
|
||||
int64_t
|
||||
SumOutputs();
|
||||
sum_outputs() const;
|
||||
|
||||
bool
|
||||
Finalise();
|
||||
finalise();
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -6,76 +6,77 @@
|
|||
namespace wallet
|
||||
{
|
||||
PendingTransaction
|
||||
TransactionConstructor::CreateTransaction(
|
||||
const std::vector<TransactionRecipient>& recipients, int64_t feePerKB) const
|
||||
TransactionConstructor::create_transaction(
|
||||
const std::vector<TransactionRecipient>& recipients) const
|
||||
{
|
||||
PendingTransaction txNew(recipients);
|
||||
SelectInputsAndFinalise(txNew, feePerKB);
|
||||
return txNew;
|
||||
PendingTransaction new_tx(recipients);
|
||||
new_tx.fee_per_byte = fee_per_byte;
|
||||
new_tx.fee_per_output = fee_per_output;
|
||||
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::SelectInputs(PendingTransaction& ptx, int64_t feePerKB) const
|
||||
TransactionConstructor::select_inputs(PendingTransaction& ptx) const
|
||||
{
|
||||
const int64_t single_input_size = 1500;
|
||||
const int64_t additional_input = 500;
|
||||
// const int64_t feePerKB = 0.000366 * 1e9;
|
||||
const int64_t dust_amount = single_input_size * feePerKB / 1000;
|
||||
int64_t estimated_fee = EstimateFee();
|
||||
// int64_t estimated_fee = estimate_fee(2, fake_outs_count, min_outputs, extra.size(), clsag,
|
||||
// base_fee, fee_percent, fixed_fee, fee_quantization_mask);
|
||||
int64_t transaction_total = ptx.SumOutputs() + estimated_fee;
|
||||
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 and prefer that the change amount
|
||||
// is enough to cover that, but if we dont have enough in the wallet then try for enough to
|
||||
// cover the fee as an additional (2nd+) input. Finally if the wallet balance is not sufficient
|
||||
// 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->prepared_get<int>(
|
||||
"SELECT sum(amount) FROM outputs WHERE amount > ?", additional_input * feePerKB / 1000);
|
||||
"SELECT sum(amount) FROM outputs WHERE amount > ?", 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 * feePerKB / 1000)
|
||||
transaction_total += single_input_size * feePerKB / 1000;
|
||||
else if (wallet_balance > transaction_total + additional_input * feePerKB / 1000)
|
||||
transaction_total += additional_input * feePerKB / 1000;
|
||||
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;
|
||||
|
||||
std::vector<Output> available_outputs{};
|
||||
SQLite::Statement st{
|
||||
db->db,
|
||||
"SELECT amount, output_index, global_index, unlock_time, block_height, spending, "
|
||||
"spent_height FROM outputs WHERE amount > ? ORDER BY amount"};
|
||||
st.bind(1, additional_input * feePerKB / 1000);
|
||||
st.bind(1, additional_input * static_cast<int64_t>(ptx.fee_per_byte));
|
||||
while (st.executeStep())
|
||||
{
|
||||
wallet::Output o(db::get<int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t>(st));
|
||||
available_outputs.push_back(o);
|
||||
}
|
||||
OutputSelector selectOutputs{};
|
||||
ptx.chosenOutputs = selectOutputs(available_outputs, transaction_total);
|
||||
ptx.UpdateChange();
|
||||
ptx.chosen_outputs = select_outputs(available_outputs, transaction_total);
|
||||
ptx.fee = ptx.get_fee();
|
||||
ptx.update_change();
|
||||
}
|
||||
|
||||
void
|
||||
TransactionConstructor::SelectInputsAndFinalise(PendingTransaction& ptx, int64_t feePerKB) const
|
||||
TransactionConstructor::select_inputs_and_finalise(PendingTransaction& ptx) const
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ptx.Finalise())
|
||||
if (ptx.finalise())
|
||||
break;
|
||||
else
|
||||
SelectInputs(ptx, feePerKB);
|
||||
select_inputs(ptx);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t
|
||||
TransactionConstructor::EstimateFee() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -18,21 +18,28 @@ namespace wallet
|
|||
{
|
||||
public:
|
||||
TransactionConstructor(std::shared_ptr<db::Database> database, std::shared_ptr<DaemonComms> dmn)
|
||||
: db(std::move(database)), daemon(std::move(dmn)){};
|
||||
: db(std::move(database)), daemon(std::move(dmn))
|
||||
{
|
||||
std::tie(fee_per_byte, fee_per_output) = daemon->get_fee_parameters();
|
||||
};
|
||||
|
||||
PendingTransaction
|
||||
CreateTransaction(const std::vector<TransactionRecipient>& recipients, int64_t feePerKB) const;
|
||||
create_transaction(const std::vector<TransactionRecipient>& recipients) const;
|
||||
|
||||
uint64_t fee_per_byte = FEE_PER_BYTE_V13;
|
||||
uint64_t fee_per_output = FEE_PER_OUTPUT_V18;
|
||||
|
||||
private:
|
||||
void
|
||||
SelectInputs(PendingTransaction& ptx, int64_t feePerKB) const;
|
||||
select_inputs(PendingTransaction& ptx) const;
|
||||
void
|
||||
SelectInputsAndFinalise(PendingTransaction& ptx, int64_t feePerKB) const;
|
||||
select_inputs_and_finalise(PendingTransaction& ptx) const;
|
||||
int64_t
|
||||
EstimateFee() const;
|
||||
estimate_fee() const;
|
||||
|
||||
std::shared_ptr<db::Database> db;
|
||||
std::shared_ptr<DaemonComms> daemon;
|
||||
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <wallet3/default_daemon_comms.hpp>
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
|
||||
class MockDaemonComms: public DefaultDaemonComms
|
||||
{
|
||||
public:
|
||||
|
||||
MockDaemonComms() : DefaultDaemonComms(get_omq()){};
|
||||
|
||||
std::shared_ptr<oxenmq::OxenMQ> get_omq() {
|
||||
return std::make_shared<oxenmq::OxenMQ>();
|
||||
}
|
||||
|
||||
std::pair<int64_t, int64_t>
|
||||
get_fee_parameters() override {
|
||||
return std::make_pair(0,0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace wallet
|
|
@ -27,10 +27,10 @@ class MockWallet : public Wallet
|
|||
|
||||
int64_t height = 0;
|
||||
|
||||
std::shared_ptr<db::Database> GetDB() { return db; };
|
||||
std::shared_ptr<db::Database> get_db() { return db; };
|
||||
|
||||
void
|
||||
StoreTestTransaction(const int64_t amount)
|
||||
store_test_transaction(const int64_t amount)
|
||||
{
|
||||
height++;
|
||||
|
||||
|
@ -38,7 +38,7 @@ class MockWallet : public Wallet
|
|||
b.height = height;
|
||||
auto hash = debug_random_filled<crypto::hash>(height);
|
||||
b.hash = hash;
|
||||
AddBlock(b);
|
||||
add_block(b);
|
||||
|
||||
std::vector<wallet::Output> dummy_outputs;
|
||||
wallet::Output o{};
|
||||
|
@ -49,7 +49,7 @@ class MockWallet : public Wallet
|
|||
dummy_outputs.push_back(o);
|
||||
|
||||
SQLite::Transaction db_tx(db->db);
|
||||
StoreTransaction(hash, height, dummy_outputs);
|
||||
store_transaction(hash, height, dummy_outputs);
|
||||
db_tx.commit();
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,33 +3,36 @@
|
|||
|
||||
#include <wallet3/wallet.hpp>
|
||||
#include <wallet3/db_schema.hpp>
|
||||
#include <wallet3/default_daemon_comms.hpp>
|
||||
|
||||
#include <sqlitedb/database.hpp>
|
||||
|
||||
#include "mock_wallet.hpp"
|
||||
#include "mock_daemon_comms.hpp"
|
||||
|
||||
|
||||
TEST_CASE("Transaction Creation", "[wallet,tx]")
|
||||
{
|
||||
auto wallet = wallet::MockWallet();
|
||||
auto ctor = wallet::TransactionConstructor(wallet.GetDB(), nullptr);
|
||||
auto comms = std::make_shared<wallet::MockDaemonComms>();
|
||||
auto ctor = wallet::TransactionConstructor(wallet.get_db(), comms);
|
||||
ctor.fee_per_byte = 0;
|
||||
ctor.fee_per_output = 0;
|
||||
SECTION("Expect Fail if database is empty")
|
||||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 4);
|
||||
REQUIRE_THROWS(ctor.CreateTransaction(recipients, {}));
|
||||
REQUIRE_THROWS(ctor.create_transaction(recipients));
|
||||
}
|
||||
|
||||
wallet.StoreTestTransaction(5);
|
||||
wallet.store_test_transaction(5);
|
||||
|
||||
SECTION("Creates a successful single transaction")
|
||||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 4);
|
||||
wallet::PendingTransaction ptx = ctor.CreateTransaction(recipients, {});
|
||||
wallet::PendingTransaction ptx = ctor.create_transaction(recipients);
|
||||
REQUIRE(ptx.recipients.size() == 1);
|
||||
REQUIRE(ptx.chosenOutputs.size() == 1);
|
||||
REQUIRE(ptx.chosen_outputs.size() == 1);
|
||||
REQUIRE(ptx.change.amount == 1);
|
||||
}
|
||||
|
||||
|
@ -37,18 +40,18 @@ TEST_CASE("Transaction Creation", "[wallet,tx]")
|
|||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 6);
|
||||
REQUIRE_THROWS(ctor.CreateTransaction(recipients, {}));
|
||||
REQUIRE_THROWS(ctor.create_transaction(recipients));
|
||||
}
|
||||
|
||||
wallet.StoreTestTransaction(5);
|
||||
wallet.StoreTestTransaction(7);
|
||||
wallet.store_test_transaction(5);
|
||||
wallet.store_test_transaction(7);
|
||||
SECTION("Creates a successful single transaction prefering to use a single input if possible")
|
||||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 6);
|
||||
wallet::PendingTransaction ptx = ctor.CreateTransaction(recipients, {});
|
||||
wallet::PendingTransaction ptx = ctor.create_transaction(recipients);
|
||||
REQUIRE(ptx.recipients.size() == 1);
|
||||
REQUIRE(ptx.chosenOutputs.size() == 1);
|
||||
REQUIRE(ptx.chosen_outputs.size() == 1);
|
||||
REQUIRE(ptx.change.amount == 1);
|
||||
}
|
||||
|
||||
|
@ -56,21 +59,35 @@ TEST_CASE("Transaction Creation", "[wallet,tx]")
|
|||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 8);
|
||||
wallet::PendingTransaction ptx = ctor.CreateTransaction(recipients, {});
|
||||
wallet::PendingTransaction ptx = ctor.create_transaction(recipients);
|
||||
REQUIRE(ptx.recipients.size() == 1);
|
||||
REQUIRE(ptx.chosenOutputs.size() == 2);
|
||||
REQUIRE(ptx.chosen_outputs.size() == 2);
|
||||
}
|
||||
|
||||
wallet.StoreTestTransaction(1000);
|
||||
wallet.StoreTestTransaction(1000);
|
||||
wallet.store_test_transaction(4000);
|
||||
wallet.store_test_transaction(4000);
|
||||
ctor.fee_per_byte = 1;
|
||||
|
||||
SECTION("Creates a successful transaction using 2 inputs and avoids creating dust")
|
||||
SECTION("Creates a successful transaction using 2 inputs, avoids creating dust and uses correct fee using 1 oxen per byte")
|
||||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 1001);
|
||||
wallet::PendingTransaction ptx = ctor.CreateTransaction(recipients, 1000);
|
||||
recipients.emplace_back(wallet::address{}, 4001);
|
||||
wallet::PendingTransaction ptx = ctor.create_transaction(recipients);
|
||||
REQUIRE(ptx.recipients.size() == 1);
|
||||
REQUIRE(ptx.chosenOutputs.size() == 2);
|
||||
REQUIRE(ptx.change.amount == 999);
|
||||
REQUIRE(ptx.chosen_outputs.size() == 2);
|
||||
// 8000 (Inputs) - 4001 (Recipient) - 1857 bytes x 1 oxen (Fee)
|
||||
REQUIRE(ptx.change.amount == 2142);
|
||||
}
|
||||
|
||||
ctor.fee_per_output = 50;
|
||||
SECTION("Creates a successful transaction using 2 inputs, avoids creating dust and uses correct fee using 1 oxen per byte and 50 oxen per output")
|
||||
{
|
||||
std::vector<wallet::TransactionRecipient> recipients;
|
||||
recipients.emplace_back(wallet::address{}, 4001);
|
||||
wallet::PendingTransaction ptx = ctor.create_transaction(recipients);
|
||||
REQUIRE(ptx.recipients.size() == 1);
|
||||
REQUIRE(ptx.chosen_outputs.size() == 2);
|
||||
// 8000 (Inputs) - 4001 (Recipient) - 1857 bytes x 1 oxen (Fee) - 100 (Fee for 2x outputs @ 50 oxen)
|
||||
REQUIRE(ptx.change.amount == 2042);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue