Skip to content

Commit

Permalink
Merge pull request #7819
Browse files Browse the repository at this point in the history
b030f20 Fee changes from ArticMine (moneromooo-monero)
9f786f0 epee: allow copying a rolling_median_t object (moneromooo-monero)
  • Loading branch information
luigi1111 committed Apr 18, 2022
2 parents f49fc9b + b030f20 commit 2b999f5
Show file tree
Hide file tree
Showing 17 changed files with 530 additions and 49 deletions.
15 changes: 14 additions & 1 deletion contrib/epee/include/rolling_median.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ struct rolling_median_t

protected:
rolling_median_t &operator=(const rolling_median_t&) = delete;
rolling_median_t(const rolling_median_t&) = delete;

public:
//creates new rolling_median_t: to calculate `nItems` running median.
Expand All @@ -139,6 +138,20 @@ struct rolling_median_t
clear();
}

rolling_median_t(const rolling_median_t &other)
{
N = other.N;
int size = N * (sizeof(Item) + sizeof(int) * 2);
data = (Item*)malloc(size);
memcpy(data, other.data, size);
pos = (int*) (data + N);
heap = pos + N + (N / 2); //points to middle of storage.
idx = other.idx;
minCt = other.minCt;
maxCt = other.maxCt;
sz = other.sz;
}

rolling_median_t(rolling_median_t &&m)
{
memcpy(this, &m, sizeof(rolling_median_t));
Expand Down
63 changes: 63 additions & 0 deletions src/cryptonote_basic/cryptonote_format_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,69 @@ namespace cryptonote
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 h = null_hash;
Expand Down
2 changes: 2 additions & 0 deletions src/cryptonote_basic/cryptonote_format_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ namespace cryptonote
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(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>
bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob)
Expand Down
2 changes: 2 additions & 0 deletions src/cryptonote_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@
#define HF_VERSION_CLSAG 13
#define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13
#define HF_VERSION_BULLETPROOF_PLUS 15
#define HF_VERSION_2021_SCALING 15

#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
#define CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES 2

#define HASH_OF_HASHES_STEP 512

Expand Down
137 changes: 130 additions & 7 deletions src/cryptonote_core/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
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);
assert(hi == 0);
lo /= 5;
return lo;
if (version >= HF_VERSION_2021_SCALING)
{
// 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;
Expand Down Expand Up @@ -3786,6 +3799,81 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const
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
{
Expand All @@ -3798,6 +3886,13 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const
if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW)
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);
std::vector<uint64_t> weights;
get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks);
Expand Down Expand Up @@ -4472,6 +4567,7 @@ bool Blockchain::check_blockchain_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
{
PERF_TIMER(get_next_long_term_block_weight);
Expand All @@ -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_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);

return long_term_block_weight;
Expand Down Expand Up @@ -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);

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);

if (db_height == 1)
Expand All @@ -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);

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;
}
Expand Down
16 changes: 16 additions & 0 deletions src/cryptonote_core/blockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,22 @@ namespace cryptonote
* @return the fee estimate
*/
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
Expand Down
12 changes: 11 additions & 1 deletion src/rpc/core_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2890,7 +2890,17 @@ namespace cryptonote
return r;

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.status = CORE_RPC_STATUS_OK;
return true;
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/core_rpc_server_commands_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2191,11 +2191,13 @@ namespace cryptonote
{
uint64_t fee;
uint64_t quantization_mask;
std::vector<uint64_t> fees;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_PARENT(rpc_access_response_base)
KV_SERIALIZE(fee)
KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1)
KV_SERIALIZE(fees)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
Expand Down
1 change: 0 additions & 1 deletion src/wallet/api/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_BULLETPROOF_PLUS, 0),
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());
}

Expand Down
Loading

0 comments on commit 2b999f5

Please sign in to comment.