Fee changes from ArticMine

https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf

with a change to use 1.7 instead of 2.0 for the max long term increase rate
This commit is contained in:
moneromooo-monero 2021-07-29 12:02:08 +00:00
parent 9f786f0550
commit b030f20751
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
15 changed files with 498 additions and 48 deletions

View file

@ -1064,6 +1064,69 @@ namespace cryptonote
return s; return s;
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
uint64_t round_money_up(uint64_t amount, unsigned significant_digits)
{
// round monetary amount up with the requested amount of significant digits
CHECK_AND_ASSERT_THROW_MES(significant_digits > 0, "significant_digits must not be 0");
static_assert(sizeof(unsigned long long) == sizeof(uint64_t), "Unexpected unsigned long long size");
// we don't need speed, so we do it via text, as it's easier to get right
char buf[32];
snprintf(buf, sizeof(buf), "%llu", (unsigned long long)amount);
const size_t len = strlen(buf);
if (len > significant_digits)
{
bool bump = false;
char *ptr;
for (ptr = buf + significant_digits; *ptr; ++ptr)
{
// bump digits by one if the following digits past significant digits were to be 5 or more
if (*ptr != '0')
{
bump = true;
*ptr = '0';
}
}
ptr = buf + significant_digits;
while (bump && ptr > buf)
{
--ptr;
// bumping a nine overflows
if (*ptr == '9')
*ptr = '0';
else
{
// bumping another digit means we can stop bumping (no carry)
++*ptr;
bump = false;
}
}
if (bump)
{
// carry reached the highest digit, we need to add a 1 in front
size_t offset = strlen(buf);
for (size_t i = offset + 1; i > 0; --i)
buf[i] = buf[i - 1];
buf[0] = '1';
}
}
char *end = NULL;
errno = 0;
const unsigned long long ull = strtoull(buf, &end, 10);
CHECK_AND_ASSERT_THROW_MES(ull != ULONG_MAX || errno == 0, "Failed to parse rounded amount: " << buf);
CHECK_AND_ASSERT_THROW_MES(ull != 0 || amount == 0, "Overflow in rounding");
return ull;
}
//---------------------------------------------------------------
std::string round_money_up(const std::string &s, unsigned significant_digits)
{
uint64_t amount;
CHECK_AND_ASSERT_THROW_MES(parse_amount(amount, s), "Failed to parse amount: " << s);
amount = round_money_up(amount, significant_digits);
return print_money(amount);
}
//---------------------------------------------------------------
crypto::hash get_blob_hash(const blobdata& blob) crypto::hash get_blob_hash(const blobdata& blob)
{ {
crypto::hash h = null_hash; crypto::hash h = null_hash;

View file

@ -144,6 +144,8 @@ namespace cryptonote
std::string get_unit(unsigned int decimal_point = -1); std::string get_unit(unsigned int decimal_point = -1);
std::string print_money(uint64_t amount, unsigned int decimal_point = -1); std::string print_money(uint64_t amount, unsigned int decimal_point = -1);
std::string print_money(const boost::multiprecision::uint128_t &amount, unsigned int decimal_point = -1); std::string print_money(const boost::multiprecision::uint128_t &amount, unsigned int decimal_point = -1);
uint64_t round_money_up(uint64_t amount, unsigned significant_digits);
std::string round_money_up(const std::string &amount, unsigned significant_digits);
//--------------------------------------------------------------- //---------------------------------------------------------------
template<class t_object> template<class t_object>
bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob) bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob)

View file

@ -183,8 +183,10 @@
#define HF_VERSION_CLSAG 13 #define HF_VERSION_CLSAG 13
#define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13 #define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13
#define HF_VERSION_BULLETPROOF_PLUS 15 #define HF_VERSION_BULLETPROOF_PLUS 15
#define HF_VERSION_2021_SCALING 15
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8
#define CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES 2
#define HASH_OF_HASHES_STEP 512 #define HASH_OF_HASHES_STEP 512

View file

@ -3710,11 +3710,24 @@ uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_b
if (version >= HF_VERSION_PER_BYTE_FEE) if (version >= HF_VERSION_PER_BYTE_FEE)
{ {
lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi); lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi);
div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL);
div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL);
assert(hi == 0); if (version >= HF_VERSION_2021_SCALING)
lo /= 5; {
return lo; // min_fee_per_byte = round_up( 0.95 * block_reward * ref_weight / (fee_median^2) )
// note: since hardfork HF_VERSION_2021_SCALING, fee_median (a.k.a. median_block_weight) equals effective long term median
div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL);
assert(hi == 0);
lo -= lo / 20;
return lo;
}
else
{
// min_fee_per_byte = 0.2 * block_reward * ref_weight / (min_penalty_free_zone * fee_median)
div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL);
assert(hi == 0);
lo /= 5;
return lo;
}
} }
const uint64_t fee_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE; const uint64_t fee_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE;
@ -3786,6 +3799,81 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const
return true; return true;
} }
//------------------------------------------------------------------
void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const
{
// variable names and calculations as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf
// from (earlier than) this fork, the base fee is per byte
const uint64_t Mfw = std::min(Mnw, Mlw);
// 3 kB divided by something ? It's going to be either 0 or *very* quantized, so fold it into integer steps below
//const uint64_t Brlw = DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / Mfw;
// constant.... equal to 0, unless floating point, so fold it into integer steps below
//const uint64_t Br = DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5
//const uint64_t Fl = base_reward * Brlw / Mfw; fold Brlw from above
const uint64_t Fl = base_reward * /*Brlw*/ DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (Mfw * Mfw);
// fold Fl into this for better precision (and to match the test cases in the PDF)
// const uint64_t Fn = 4 * Fl;
const uint64_t Fn = 4 * base_reward * /*Brlw*/ DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (Mfw * Mfw);
// const uint64_t Fm = 16 * base_reward * Br / Mfw; fold Br from above
const uint64_t Fm = 16 * base_reward * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 * Mfw);
// const uint64_t Fp = 2 * base_reward / Mnw;
// fold Br from above, move 4Fm in the max to decrease quantization effect
//const uint64_t Fh = 4 * Fm * std::max<uint64_t>(1, Mfw / (32 * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT * Mnw / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5));
const uint64_t Fh = std::max<uint64_t>(4 * Fm, 4 * Fm * Mfw / (32 * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT * Mnw / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5));
fees.resize(4);
fees[0] = cryptonote::round_money_up(Fl, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES);
fees[1] = cryptonote::round_money_up(Fn, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES);
fees[2] = cryptonote::round_money_up(Fm, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES);
fees[3] = cryptonote::round_money_up(Fh, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES);
}
void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const
{
const uint8_t version = get_current_hard_fork_version();
const uint64_t db_height = m_db->height();
// we want Mlw = median of max((min(Mbw, 1.7 * Ml), Zm), Ml / 1.7)
// Mbw: block weight for the last 99990 blocks, 0 for the next 10
// Ml: penalty free zone (dynamic), aka long_term_median, aka median of max((min(Mb, 1.7 * Ml), Zm), Ml / 1.7)
// Zm: 300000 (minimum penalty free zone)
//
// So we copy the current rolling median state, add 10 (grace_blocks) zeroes to it, and get back Mlw
epee::misc_utils::rolling_median_t<uint64_t> rm = m_long_term_block_weights_cache_rolling_median;
for (size_t i = 0; i < grace_blocks; ++i)
rm.insert(0);
const uint64_t Mlw_penalty_free_zone_for_wallet = std::max<uint64_t>(rm.median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5);
// Msw: median over [100 - grace blocks] past + [grace blocks] future blocks
CHECK_AND_ASSERT_THROW_MES(grace_blocks <= 100, "Grace blocks invalid In 2021 fee scaling estimate.");
std::vector<uint64_t> weights;
get_last_n_blocks_weights(weights, 100 - grace_blocks);
weights.reserve(100);
for (size_t i = 0; i < grace_blocks; ++i)
weights.push_back(0);
const uint64_t Msw_effective_short_term_median = std::max(epee::misc_utils::median(weights), Mlw_penalty_free_zone_for_wallet);
const uint64_t Mnw = std::min(Msw_effective_short_term_median, 50 * Mlw_penalty_free_zone_for_wallet);
uint64_t already_generated_coins = db_height ? m_db->get_block_already_generated_coins(db_height - 1) : 0;
uint64_t base_reward;
if (!get_block_reward(m_current_block_cumul_weight_limit / 2, 1, already_generated_coins, base_reward, version))
{
MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound");
base_reward = BLOCK_REWARD_OVERESTIMATE;
}
get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, base_reward, Mnw, Mlw_penalty_free_zone_for_wallet, fees);
}
//------------------------------------------------------------------ //------------------------------------------------------------------
uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const
{ {
@ -3798,6 +3886,13 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const
if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW)
grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1;
if (version >= HF_VERSION_2021_SCALING)
{
std::vector<uint64_t> fees;
get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees);
return fees[0];
}
const uint64_t min_block_weight = get_min_block_weight(version); const uint64_t min_block_weight = get_min_block_weight(version);
std::vector<uint64_t> weights; std::vector<uint64_t> weights;
get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks);
@ -4472,6 +4567,7 @@ bool Blockchain::check_blockchain_pruning()
return m_db->check_pruning(); return m_db->check_pruning();
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
// returns min(Mb, 1.7*Ml) as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf from HF_VERSION_LONG_TERM_BLOCK_WEIGHT
uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) const uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) const
{ {
PERF_TIMER(get_next_long_term_block_weight); PERF_TIMER(get_next_long_term_block_weight);
@ -4486,7 +4582,18 @@ uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) cons
uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks);
uint64_t long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); uint64_t long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median);
uint64_t short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; uint64_t short_term_constraint;
if (hf_version >= HF_VERSION_2021_SCALING)
{
// long_term_block_weight = block_weight bounded to range [long-term-median/1.7, long-term-median*1.7]
block_weight = std::max<uint64_t>(block_weight, long_term_effective_median_block_weight * 10 / 17);
short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 7 / 10;
}
else
{
// long_term_block_weight = block_weight bounded to range [0, long-term-median*1.4]
short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5;
}
uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint);
return long_term_block_weight; return long_term_block_weight;
@ -4528,7 +4635,11 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti
m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median);
uint64_t short_term_constraint = m_long_term_effective_median_block_weight + m_long_term_effective_median_block_weight * 2 / 5; uint64_t short_term_constraint = m_long_term_effective_median_block_weight;
if (hf_version >= HF_VERSION_2021_SCALING)
short_term_constraint += m_long_term_effective_median_block_weight * 7 / 10;
else
short_term_constraint += m_long_term_effective_median_block_weight * 2 / 5;
uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint);
if (db_height == 1) if (db_height == 1)
@ -4547,7 +4658,19 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti
get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW);
uint64_t short_term_median = epee::misc_utils::median(weights); uint64_t short_term_median = epee::misc_utils::median(weights);
uint64_t effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); uint64_t effective_median_block_weight;
if (hf_version >= HF_VERSION_2021_SCALING)
{
// effective median = short_term_median bounded to range [long_term_median, 50*long_term_median], but it can't be smaller than the
// minimum penalty free zone (a.k.a. 'full reward zone')
effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(m_long_term_effective_median_block_weight, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight);
}
else
{
// effective median = short_term_median bounded to range [0, 50*long_term_median], but it can't be smaller than the
// minimum penalty free zone (a.k.a. 'full reward zone')
effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight);
}
m_current_block_cumul_weight_median = effective_median_block_weight; m_current_block_cumul_weight_median = effective_median_block_weight;
} }

View file

@ -648,6 +648,22 @@ namespace cryptonote
* @return the fee estimate * @return the fee estimate
*/ */
uint64_t get_dynamic_base_fee_estimate(uint64_t grace_blocks) const; uint64_t get_dynamic_base_fee_estimate(uint64_t grace_blocks) const;
void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const;
/**
* @brief get four levels of dynamic per byte fee estimate for the next few blocks
*
* The dynamic fee is based on the block weight in a past window, and
* the current block reward. It is expressed per byte, and is based on
* https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf
* This function calculates an estimate for a dynamic fee which will be
* valid for the next grace_blocks
*
* @param grace_blocks number of blocks we want the fee to be valid for
*
* @return the fee estimates (4 of them)
*/
void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const;
/** /**
* @brief validate a transaction's fee * @brief validate a transaction's fee

View file

@ -2890,7 +2890,17 @@ namespace cryptonote
return r; return r;
CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE); CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE);
res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks);
const uint8_t version = m_core.get_blockchain_storage().get_current_hard_fork_version();
if (version >= HF_VERSION_2021_SCALING)
{
m_core.get_blockchain_storage().get_dynamic_base_fee_estimate_2021_scaling(req.grace_blocks, res.fees);
res.fee = res.fees[0];
}
else
{
res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks);
}
res.quantization_mask = Blockchain::get_fee_quantization_mask(); res.quantization_mask = Blockchain::get_fee_quantization_mask();
res.status = CORE_RPC_STATUS_OK; res.status = CORE_RPC_STATUS_OK;
return true; return true;

View file

@ -2191,11 +2191,13 @@ namespace cryptonote
{ {
uint64_t fee; uint64_t fee;
uint64_t quantization_mask; uint64_t quantization_mask;
std::vector<uint64_t> fees;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_PARENT(rpc_access_response_base)
KV_SERIALIZE(fee) KV_SERIALIZE(fee)
KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1)
KV_SERIALIZE(fees)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
typedef epee::misc_utils::struct_init<response_t> response; typedef epee::misc_utils::struct_init<response_t> response;

View file

@ -1744,7 +1744,6 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str
m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0),
m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0), m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0),
m_wallet->get_base_fee(), m_wallet->get_base_fee(),
m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))),
m_wallet->get_fee_quantization_mask()); m_wallet->get_fee_quantization_mask());
} }

View file

@ -70,6 +70,7 @@ void NodeRPCProxy::invalidate()
m_dynamic_base_fee_estimate = 0; m_dynamic_base_fee_estimate = 0;
m_dynamic_base_fee_estimate_cached_height = 0; m_dynamic_base_fee_estimate_cached_height = 0;
m_dynamic_base_fee_estimate_grace_blocks = 0; m_dynamic_base_fee_estimate_grace_blocks = 0;
m_dynamic_base_fee_estimate_vector.clear();
m_fee_quantization_mask = 1; m_fee_quantization_mask = 1;
m_rpc_version = 0; m_rpc_version = 0;
m_target_height = 0; m_target_height = 0;
@ -210,7 +211,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
return boost::optional<std::string>(); return boost::optional<std::string>();
} }
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees)
{ {
uint64_t height; uint64_t height;
@ -238,13 +239,24 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
m_dynamic_base_fee_estimate = resp_t.fee; m_dynamic_base_fee_estimate = resp_t.fee;
m_dynamic_base_fee_estimate_cached_height = height; m_dynamic_base_fee_estimate_cached_height = height;
m_dynamic_base_fee_estimate_grace_blocks = grace_blocks; m_dynamic_base_fee_estimate_grace_blocks = grace_blocks;
m_dynamic_base_fee_estimate_vector = !resp_t.fees.empty() ? std::move(resp_t.fees) : std::vector<uint64_t>{m_dynamic_base_fee_estimate};
m_fee_quantization_mask = resp_t.quantization_mask; m_fee_quantization_mask = resp_t.quantization_mask;
} }
fee = m_dynamic_base_fee_estimate; fees = m_dynamic_base_fee_estimate_vector;
return boost::optional<std::string>(); return boost::optional<std::string>();
} }
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee)
{
std::vector<uint64_t> fees;
auto res = get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees);
if (res)
return res;
fee = fees[0];
return boost::none;
}
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask)
{ {
uint64_t height; uint64_t height;

View file

@ -56,6 +56,7 @@ public:
boost::optional<std::string> get_adjusted_time(uint64_t &adjusted_time); boost::optional<std::string> get_adjusted_time(uint64_t &adjusted_time);
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height); boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height);
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee); boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee);
boost::optional<std::string> get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees);
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask); boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask);
boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
@ -85,6 +86,7 @@ private:
uint64_t m_dynamic_base_fee_estimate; uint64_t m_dynamic_base_fee_estimate;
uint64_t m_dynamic_base_fee_estimate_cached_height; uint64_t m_dynamic_base_fee_estimate_cached_height;
uint64_t m_dynamic_base_fee_estimate_grace_blocks; uint64_t m_dynamic_base_fee_estimate_grace_blocks;
std::vector<uint64_t> m_dynamic_base_fee_estimate_vector;
uint64_t m_fee_quantization_mask; uint64_t m_fee_quantization_mask;
uint64_t m_adjusted_time; uint64_t m_adjusted_time;
uint32_t m_rpc_version; uint32_t m_rpc_version;

View file

@ -290,15 +290,15 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file,
mms_file = file_path + ".mms"; mms_file = file_path + ".mms";
} }
uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier) uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes)
{ {
uint64_t kB = (bytes + 1023) / 1024; uint64_t kB = (bytes + 1023) / 1024;
return kB * fee_per_kb * fee_multiplier; return kB * fee_per_kb;
} }
uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_multiplier, uint64_t fee_quantization_mask) uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_quantization_mask)
{ {
uint64_t fee = weight * base_fee * fee_multiplier; uint64_t fee = weight * base_fee;
fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask; fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask;
return fee; return fee;
} }
@ -878,12 +878,12 @@ uint8_t get_clsag_fork()
return HF_VERSION_CLSAG; return HF_VERSION_CLSAG;
} }
uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_quantization_mask)
{ {
if (use_per_byte_fee) if (use_per_byte_fee)
return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_multiplier, fee_quantization_mask); return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_quantization_mask);
else else
return calculate_fee(base_fee, blob_size, fee_multiplier); return calculate_fee(base_fee, blob_size);
} }
bool get_short_payment_id(crypto::hash8 &payment_id8, const tools::wallet2::pending_tx &ptx, hw::device &hwdev) bool get_short_payment_id(crypto::hash8 &payment_id8, const tools::wallet2::pending_tx &ptx, hw::device &hwdev)
@ -7232,17 +7232,17 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
return sign_multisig_tx_to_file(exported_txs, filename, txids); return sign_multisig_tx_to_file(exported_txs, filename, txids);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_quantization_mask) const
{ {
if (use_per_byte_fee) if (use_per_byte_fee)
{ {
const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_quantization_mask);
} }
else else
{ {
const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus);
return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); return calculate_fee(base_fee, estimated_tx_size);
} }
} }
@ -7315,6 +7315,40 @@ uint64_t wallet2::get_base_fee()
return get_dynamic_base_fee_estimate(); return get_dynamic_base_fee_estimate();
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_base_fee(uint32_t priority)
{
const bool use_2021_scaling = use_fork_rules(HF_VERSION_2021_SCALING, -30 * 1);
if (use_2021_scaling)
{
// clamp and map to 0..3 indices, mapping 0 (default, but should not end up here) to 0, and 1..4 to 0..3
if (priority == 0)
priority = 1;
else if (priority > 4)
priority = 4;
--priority;
std::vector<uint64_t> fees;
boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_base_fee_estimate_2021_scaling(FEE_ESTIMATE_GRACE_BLOCKS, fees);
if (result)
{
MERROR("Failed to determine base fee, using default");
return FEE_PER_BYTE;
}
if (priority >= fees.size())
{
MERROR("Failed to determine base fee for priority " << priority << ", using default");
return FEE_PER_BYTE;
}
return fees[priority];
}
else
{
const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority);
return base_fee * fee_multiplier;
}
}
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_fee_quantization_mask() uint64_t wallet2::get_fee_quantization_mask()
{ {
if(m_light_wallet) if(m_light_wallet)
@ -7389,9 +7423,8 @@ uint32_t wallet2::adjust_priority(uint32_t priority)
{ {
// check if there's a backlog in the tx pool // check if there's a backlog in the tx pool
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee(1);
const uint64_t fee_multiplier = get_fee_multiplier(1); const double fee_level = base_fee * (use_per_byte_fee ? 1 : (12/(double)13 / (double)1024));
const double fee_level = fee_multiplier * base_fee * (use_per_byte_fee ? 1 : (12/(double)13 / (double)1024));
const std::vector<std::pair<uint64_t, uint64_t>> blocks = estimate_backlog({std::make_pair(fee_level, fee_level)}); const std::vector<std::pair<uint64_t, uint64_t>> blocks = estimate_backlog({std::make_pair(fee_level, fee_level)});
if (blocks.size() != 1) if (blocks.size() != 1)
{ {
@ -9665,8 +9698,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
bulletproof_plus ? 4 : 3 bulletproof_plus ? 4 : 3
}; };
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee(priority);
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
const uint64_t fee_quantization_mask = get_fee_quantization_mask(); const uint64_t fee_quantization_mask = get_fee_quantization_mask();
// throw if attempting a transaction with no destinations // throw if attempting a transaction with no destinations
@ -9698,7 +9730,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// early out if we know we can't make it anyway // early out if we know we can't make it anyway
// we could also check for being within FEE_PER_KB, but if the fee calculation // we could also check for being within FEE_PER_KB, but if the fee calculation
// ever changes, this might be missed, so let this go through // ever changes, this might be missed, so let this go through
const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus)); const uint64_t min_fee = (base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus));
uint64_t balance_subtotal = 0; uint64_t balance_subtotal = 0;
uint64_t unlocked_balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0;
for (uint32_t index_minor : subaddr_indices) for (uint32_t index_minor : subaddr_indices)
@ -9720,7 +9752,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
// gather all dust and non-dust outputs belonging to specified subaddresses // gather all dust and non-dust outputs belonging to specified subaddresses
size_t num_nondust_outputs = 0; size_t num_nondust_outputs = 0;
@ -9814,7 +9846,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
{ {
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee. // will get us a known fee.
uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_quantization_mask);
preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty()) if (!preferred_inputs.empty())
{ {
@ -9994,7 +10026,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
pending_tx test_ptx; pending_tx test_ptx;
const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_quantization_mask);
auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee) auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee)
{ {
@ -10048,7 +10080,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0);
LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" << LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" <<
print_money(needed_fee) << " needed)"); print_money(needed_fee) << " needed)");
@ -10073,7 +10105,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
txBlob = t_serializable_object_to_blob(test_ptx.tx); txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) <<
" fee and " << print_money(test_ptx.change_dts.amount) << " change"); " fee and " << print_money(test_ptx.change_dts.amount) << " change");
} }
@ -10256,13 +10288,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee(priority);
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus);
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account"); THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account");
@ -10371,8 +10402,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
rct::RangeProofPaddedBulletproof, rct::RangeProofPaddedBulletproof,
bulletproof_plus ? 4 : 3 bulletproof_plus ? 4 : 3
}; };
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee(priority);
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
const uint64_t fee_quantization_mask = get_fee_quantization_mask(); const uint64_t fee_quantization_mask = get_fee_quantization_mask();
LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs");
@ -10399,11 +10429,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) if (use_fork_rules(HF_VERSION_PER_BYTE_FEE))
{ {
const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus); const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus);
fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_quantization_mask);
} }
else else
{ {
fee_dust_threshold = base_fee * fee_multiplier * (upper_transaction_weight_limit + 1023) / 1024; fee_dust_threshold = base_fee * (upper_transaction_weight_limit + 1023) / 1024;
} }
size_t idx = size_t idx =
@ -10437,7 +10467,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
pending_tx test_ptx; pending_tx test_ptx;
const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_quantization_mask);
// add N - 1 outputs for correct initial fee estimation // add N - 1 outputs for correct initial fee estimation
for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i)
@ -10452,7 +10482,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; available_for_fee = test_ptx.fee + test_ptx.change_dts.amount;
for (auto &dt: test_ptx.dests) for (auto &dt: test_ptx.dests)
available_for_fee += dt.amount; available_for_fee += dt.amount;
@ -10489,7 +10519,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
txBlob = t_serializable_object_to_blob(test_ptx.tx); txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask);
LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) <<
" fee and " << print_money(test_ptx.change_dts.amount) << " change"); " fee and " << print_money(test_ptx.change_dts.amount) << " change");
} while (needed_fee > test_ptx.fee); } while (needed_fee > test_ptx.fee);
@ -10816,7 +10846,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions()
const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2 const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2
tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD);
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee(1);
// may throw // may throw
std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(); std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs();

View file

@ -1427,8 +1427,9 @@ private:
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_quantization_mask) const;
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
uint64_t get_base_fee(uint32_t priority);
uint64_t get_base_fee(); uint64_t get_base_fee();
uint64_t get_fee_quantization_mask(); uint64_t get_fee_quantization_mask();
uint64_t get_min_ring_size(); uint64_t get_min_ring_size();

View file

@ -81,10 +81,10 @@ class BlockchainTest():
assert ok assert ok
res = daemon.get_fee_estimate() res = daemon.get_fee_estimate()
assert res.fee == 234562 assert res.fee == 1200000
assert res.quantization_mask == 10000 assert res.quantization_mask == 10000
res = daemon.get_fee_estimate(10) res = daemon.get_fee_estimate(10)
assert res.fee <= 234562 assert res.fee <= 1200000
# generate blocks # generate blocks
res_generateblocks = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks) res_generateblocks = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
@ -243,10 +243,10 @@ class BlockchainTest():
assert res.histogram[i].recent_instances == 0 assert res.histogram[i].recent_instances == 0
res = daemon.get_fee_estimate() res = daemon.get_fee_estimate()
assert res.fee == 234560 assert res.fee == 1200000
assert res.quantization_mask == 10000 assert res.quantization_mask == 10000
res = daemon.get_fee_estimate(10) res = daemon.get_fee_estimate(10)
assert res.fee <= 234560 assert res.fee <= 1200000
def _test_alt_chains(self): def _test_alt_chains(self):
print('Testing alt chains') print('Testing alt chains')

View file

@ -77,6 +77,7 @@ set(unit_tests_sources
pruning.cpp pruning.cpp
random.cpp random.cpp
rolling_median.cpp rolling_median.cpp
scaling_2021.cpp
serialization.cpp serialization.cpp
sha256.cpp sha256.cpp
slow_memmem.cpp slow_memmem.cpp

View file

@ -0,0 +1,187 @@
// Copyright (c) 2019-2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// References:
// - https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021.pdf
// - https://github.com/monero-project/research-lab/issues/70
#define IN_UNIT_TESTS
#include "gtest/gtest.h"
#include "cryptonote_core/blockchain.h"
#include "cryptonote_core/tx_pool.h"
#include "cryptonote_core/cryptonote_core.h"
#include "blockchain_db/testdb.h"
namespace
{
class TestDB: public cryptonote::BaseTestDB
{
public:
TestDB() { m_open = true; }
};
}
#define PREFIX_WINDOW(hf_version,window) \
std::unique_ptr<cryptonote::Blockchain> bc; \
cryptonote::tx_memory_pool txpool(*bc); \
bc.reset(new cryptonote::Blockchain(txpool)); \
struct get_test_options { \
const std::pair<uint8_t, uint64_t> hard_forks[3]; \
const cryptonote::test_options test_options = { \
hard_forks, \
window, \
}; \
get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)1), std::make_pair((uint8_t)0, (uint64_t)0)} {} \
} opts; \
cryptonote::Blockchain *blockchain = bc.get(); \
bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \
ASSERT_TRUE(r)
#define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW)
TEST(fee_2021_scaling, relay_fee_cases_from_pdf)
{
PREFIX_WINDOW(HF_VERSION_2021_SCALING, CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE);
ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 300000, HF_VERSION_2021_SCALING-1), 8000);
ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 300000, HF_VERSION_2021_SCALING), 38000);
ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1425000, HF_VERSION_2021_SCALING-1), 1684 /*1680*/);
ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1425000, HF_VERSION_2021_SCALING), 1684 /*1680*/);
ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1500000, HF_VERSION_2021_SCALING-1), 1600);
ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1500000, HF_VERSION_2021_SCALING), 1520);
ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 300000, HF_VERSION_2021_SCALING-1), 4000);
ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 300000, HF_VERSION_2021_SCALING), 19000);
ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1425000, HF_VERSION_2021_SCALING-1), 842 /*840*/);
ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1425000, HF_VERSION_2021_SCALING), 842 /*840*/);
ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1500000, HF_VERSION_2021_SCALING-1), 800);
ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1500000, HF_VERSION_2021_SCALING), 760);
}
TEST(fee_2021_scaling, wallet_fee_cases_from_pdf)
{
PREFIX_WINDOW(HF_VERSION_2021_SCALING, CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE);
std::vector<uint64_t> fees;
fees.clear();
bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 300000, 300000, fees);
ASSERT_EQ(fees.size(), 4);
ASSERT_EQ(fees[0], 20000);
ASSERT_EQ(fees[1], 80000);
ASSERT_EQ(fees[2], 320000);
ASSERT_EQ(fees[3], 4000000);
fees.clear();
bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 15000000, 300000, fees);
ASSERT_EQ(fees.size(), 4);
ASSERT_EQ(fees[0], 20000);
ASSERT_EQ(fees[1], 80000);
ASSERT_EQ(fees[2], 320000);
ASSERT_EQ(fees[3], 1300000);
fees.clear();
bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 1425000, 1425000, fees);
ASSERT_EQ(fees.size(), 4);
ASSERT_EQ(fees[0], 890);
ASSERT_EQ(fees[1], 3600);
ASSERT_EQ(fees[2], 68000);
ASSERT_EQ(fees[3], 850000 /* 842000 */);
fees.clear();
bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 1500000, 1500000, fees);
ASSERT_EQ(fees.size(), 4);
ASSERT_EQ(fees[0], 800);
ASSERT_EQ(fees[1], 3200);
ASSERT_EQ(fees[2], 64000);
ASSERT_EQ(fees[3], 800000);
fees.clear();
bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 75000000, 1500000, fees);
ASSERT_EQ(fees.size(), 4);
ASSERT_EQ(fees[0], 800);
ASSERT_EQ(fees[1], 3200);
ASSERT_EQ(fees[2], 64000);
ASSERT_EQ(fees[3], 260000);
}
TEST(fee_2021_scaling, rounding)
{
ASSERT_EQ(cryptonote::round_money_up("27810", 3), "27900.000000000000");
ASSERT_EQ(cryptonote::round_money_up("37.94", 3), "38.000000000000");
ASSERT_EQ(cryptonote::round_money_up("0.5555", 3), "0.556000000000");
ASSERT_EQ(cryptonote::round_money_up("0.002342", 3), "0.002350000000");
ASSERT_EQ(cryptonote::round_money_up("27810", 2), "28000.000000000000");
ASSERT_EQ(cryptonote::round_money_up("37.94", 2), "38.000000000000");
ASSERT_EQ(cryptonote::round_money_up("0.5555", 2), "0.560000000000");
ASSERT_EQ(cryptonote::round_money_up("0.002342", 2), "0.002400000000");
ASSERT_EQ(cryptonote::round_money_up("0", 8), "0.000000000000");
ASSERT_EQ(cryptonote::round_money_up("0.0", 8), "0.000000000000");
ASSERT_EQ(cryptonote::round_money_up("50.0", 8), "50.000000000000");
ASSERT_EQ(cryptonote::round_money_up("0.002342", 8), "0.002342000000");
ASSERT_EQ(cryptonote::round_money_up("0.002342", 1), "0.003000000000");
ASSERT_EQ(cryptonote::round_money_up("12345", 8), "12345.000000000000");
ASSERT_EQ(cryptonote::round_money_up("45678", 1), "50000.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.234", 1), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.0000001", 4), "1.001000000000");
ASSERT_EQ(cryptonote::round_money_up("1.0020001", 4), "1.003000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 1), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 2), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 3), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 4), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 5), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 6), "2.000000000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 7), "1.999999000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 8), "1.999999000000");
ASSERT_EQ(cryptonote::round_money_up("1.999999", 9), "1.999999000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 1), "3.000000000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 2), "2.100000000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 3), "2.010000000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 4), "2.001000000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 5), "2.000100000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 6), "2.000010000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 7), "2.000001000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 8), "2.000001000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 9), "2.000001000000");
ASSERT_EQ(cryptonote::round_money_up("2.000001", 4000), "2.000001000000");
ASSERT_EQ(cryptonote::round_money_up("999", 2), "1000.000000000000");
ASSERT_THROW(cryptonote::round_money_up("1.23", 0), std::runtime_error);
ASSERT_THROW(cryptonote::round_money_up("18446744.073709551615", 1), std::runtime_error);
ASSERT_THROW(cryptonote::round_money_up("18446744.073709551615", 2), std::runtime_error);
ASSERT_THROW(cryptonote::round_money_up("18446744.073709551615", 12), std::runtime_error);
ASSERT_THROW(cryptonote::round_money_up("18446744.073709551615", 19), std::runtime_error);
ASSERT_EQ(cryptonote::round_money_up("18446744.073709551615", 20), "18446744.073709551615");
}