2023-10-02 20:07:11 +00:00
//! # Block Weights
//!
//! This module contains calculations for block weights, including calculating block weight
//! limits, effective medians and long term block weights.
//!
//! For more information please see the [block weights chapter](https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html)
//! in the Monero Book.
//!
2023-10-22 16:27:37 +00:00
use std ::{
cmp ::{ max , min } ,
collections ::VecDeque ,
ops ::Range ,
} ;
2023-09-28 11:21:06 +00:00
use monero_serai ::{ block ::Block , transaction ::Transaction } ;
2023-10-24 01:25:11 +00:00
use rayon ::prelude ::* ;
2023-09-28 11:21:06 +00:00
use tower ::ServiceExt ;
use tracing ::instrument ;
2023-10-15 19:35:33 +00:00
use crate ::{
2023-10-22 16:27:37 +00:00
helper ::median , ConsensusError , Database , DatabaseRequest , DatabaseResponse , HardFork ,
2023-10-15 19:35:33 +00:00
} ;
2023-09-28 11:21:06 +00:00
2023-10-28 23:39:22 +00:00
#[ cfg(test) ]
mod tests ;
2023-09-28 11:21:06 +00:00
const PENALTY_FREE_ZONE_1 : usize = 20000 ;
const PENALTY_FREE_ZONE_2 : usize = 60000 ;
const PENALTY_FREE_ZONE_5 : usize = 300000 ;
const SHORT_TERM_WINDOW : u64 = 100 ;
const LONG_TERM_WINDOW : u64 = 100000 ;
/// Calculates the blocks weight.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#blocks-weight
pub fn block_weight ( block : & Block , txs : & [ Transaction ] ) -> usize {
txs . iter ( )
. chain ( [ & block . miner_tx ] )
. map ( | tx | tx . weight ( ) )
. sum ( )
}
/// Returns the penalty free zone
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone
pub fn penalty_free_zone ( hf : & HardFork ) -> usize {
if hf = = & HardFork ::V1 {
PENALTY_FREE_ZONE_1
} else if hf . in_range ( & HardFork ::V2 , & HardFork ::V5 ) {
PENALTY_FREE_ZONE_2
} else {
PENALTY_FREE_ZONE_5
}
}
2023-10-15 19:35:33 +00:00
/// Configuration for the block weight cache.
///
#[ derive(Debug, Clone) ]
pub struct BlockWeightsCacheConfig {
short_term_window : u64 ,
long_term_window : u64 ,
}
impl BlockWeightsCacheConfig {
2023-10-28 23:39:22 +00:00
pub const fn new ( short_term_window : u64 , long_term_window : u64 ) -> BlockWeightsCacheConfig {
2023-10-15 19:35:33 +00:00
BlockWeightsCacheConfig {
short_term_window ,
long_term_window ,
}
}
pub fn main_net ( ) -> BlockWeightsCacheConfig {
BlockWeightsCacheConfig {
short_term_window : SHORT_TERM_WINDOW ,
long_term_window : LONG_TERM_WINDOW ,
}
}
}
2023-10-02 20:07:11 +00:00
/// A cache used to calculate block weight limits, the effective median and
/// long term block weights.
///
/// These calculations require a lot of data from the database so by caching
/// this data it reduces the load on the database.
2023-10-03 21:10:31 +00:00
#[ derive(Clone) ]
2023-09-28 11:21:06 +00:00
pub struct BlockWeightsCache {
2023-10-02 20:07:11 +00:00
short_term_block_weights : VecDeque < usize > ,
2023-10-24 01:25:11 +00:00
long_term_weights : VecDeque < usize > ,
2023-09-28 11:21:06 +00:00
/// The height of the top block.
tip_height : u64 ,
2023-10-15 19:35:33 +00:00
config : BlockWeightsCacheConfig ,
2023-09-28 11:21:06 +00:00
}
impl BlockWeightsCache {
2023-10-02 20:07:11 +00:00
/// Initialize the [`BlockWeightsCache`] at the the height of the database.
2023-10-15 19:35:33 +00:00
pub async fn init < D : Database + Clone > (
config : BlockWeightsCacheConfig ,
mut database : D ,
) -> Result < Self , ConsensusError > {
2023-10-22 16:27:37 +00:00
let DatabaseResponse ::ChainHeight ( chain_height , _ ) = database
2023-09-28 11:21:06 +00:00
. ready ( )
. await ?
. call ( DatabaseRequest ::ChainHeight )
. await ?
else {
panic! ( " Database sent incorrect response! " ) ;
} ;
2023-10-15 19:35:33 +00:00
Self ::init_from_chain_height ( chain_height , config , database ) . await
2023-09-28 11:21:06 +00:00
}
2023-10-02 20:07:11 +00:00
/// Initialize the [`BlockWeightsCache`] at the the given chain height.
2023-10-15 19:35:33 +00:00
#[ instrument(name = " init_weight_cache " , level = " info " , skip(database, config)) ]
2023-09-28 11:21:06 +00:00
pub async fn init_from_chain_height < D : Database + Clone > (
chain_height : u64 ,
2023-10-15 19:35:33 +00:00
config : BlockWeightsCacheConfig ,
2023-09-28 11:21:06 +00:00
database : D ,
2023-10-02 20:07:11 +00:00
) -> Result < Self , ConsensusError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " Initializing weight cache this may take a while. " ) ;
2023-10-24 01:25:11 +00:00
let long_term_weights : VecDeque < usize > = get_long_term_weight_in_range (
2023-10-15 19:35:33 +00:00
chain_height . saturating_sub ( config . long_term_window ) .. chain_height ,
2023-09-28 11:21:06 +00:00
database . clone ( ) ,
)
2023-10-24 01:25:11 +00:00
. await ?
. into ( ) ;
2023-09-28 11:21:06 +00:00
2023-10-02 20:07:11 +00:00
let short_term_block_weights : VecDeque < usize > = get_blocks_weight_in_range (
2023-10-15 19:35:33 +00:00
chain_height . saturating_sub ( config . short_term_window ) .. chain_height ,
2023-09-28 11:21:06 +00:00
database ,
)
2023-10-02 20:07:11 +00:00
. await ?
. into ( ) ;
tracing ::info! ( " Initialized block weight cache, chain-height: {:?}, long term weights length: {:?}, short term weights length: {:?} " , chain_height , long_term_weights . len ( ) , short_term_block_weights . len ( ) ) ;
2023-09-28 11:21:06 +00:00
Ok ( BlockWeightsCache {
short_term_block_weights ,
long_term_weights ,
tip_height : chain_height - 1 ,
2023-10-15 19:35:33 +00:00
config ,
2023-09-28 11:21:06 +00:00
} )
}
2023-10-02 20:07:11 +00:00
/// Add a new block to the cache.
///
/// The block_height **MUST** be one more than the last height the cache has
/// seen.
2023-10-28 23:39:22 +00:00
pub fn new_block ( & mut self , block_height : u64 , block_weight : usize , long_term_weight : usize ) {
2023-10-24 01:25:11 +00:00
assert_eq! ( self . tip_height + 1 , block_height ) ;
self . tip_height + = 1 ;
2023-10-02 20:07:11 +00:00
tracing ::debug! (
" Adding new block's {} weights to block cache, weight: {}, long term weight: {} " ,
2023-10-24 01:25:11 +00:00
self . tip_height ,
2023-10-02 20:07:11 +00:00
block_weight ,
long_term_weight
) ;
2023-10-24 01:25:11 +00:00
self . long_term_weights . push_back ( long_term_weight ) ;
2023-10-23 18:14:40 +00:00
if u64 ::try_from ( self . long_term_weights . len ( ) ) . unwrap ( ) > self . config . long_term_window {
2023-10-24 01:25:11 +00:00
self . long_term_weights . pop_front ( ) ;
2023-10-02 20:07:11 +00:00
}
self . short_term_block_weights . push_back ( block_weight ) ;
2023-10-24 01:25:11 +00:00
if u64 ::try_from ( self . short_term_block_weights . len ( ) ) . unwrap ( )
> self . config . short_term_window
{
2023-10-02 20:07:11 +00:00
self . short_term_block_weights . pop_front ( ) ;
}
}
2023-10-22 16:27:37 +00:00
/// Returns the median long term weight over the last [`LONG_TERM_WINDOW`] blocks, or custom amount of blocks in the config.
pub fn median_long_term_weight ( & self ) -> usize {
2023-10-24 01:25:11 +00:00
let mut sorted_long_term_weights : Vec < usize > = self . long_term_weights . clone ( ) . into ( ) ;
sorted_long_term_weights . par_sort_unstable ( ) ;
median ( & sorted_long_term_weights )
2023-10-22 16:27:37 +00:00
}
2023-10-28 23:39:22 +00:00
pub fn median_short_term_weight ( & self ) -> usize {
let mut sorted_short_term_block_weights : Vec < usize > =
self . short_term_block_weights . clone ( ) . into ( ) ;
sorted_short_term_block_weights . sort_unstable ( ) ;
median ( & sorted_short_term_block_weights )
}
2023-10-02 20:07:11 +00:00
/// Returns the effective median weight, used for block reward calculations and to calculate
/// the block weight limit.
///
/// See: https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#calculating-effective-median-weight
pub fn effective_median_block_weight ( & self , hf : & HardFork ) -> usize {
calculate_effective_median_block_weight (
hf ,
2023-10-28 23:39:22 +00:00
self . median_short_term_weight ( ) ,
self . median_long_term_weight ( ) ,
2023-10-02 20:07:11 +00:00
)
}
2023-10-15 19:35:33 +00:00
/// Returns the median weight used to calculate block reward punishment.
///
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/reward.html#calculating-block-reward
pub fn median_for_block_reward ( & self , hf : & HardFork ) -> usize {
if hf . in_range ( & HardFork ::V1 , & HardFork ::V12 ) {
2023-10-28 23:39:22 +00:00
self . median_short_term_weight ( )
2023-10-15 19:35:33 +00:00
} else {
self . effective_median_block_weight ( hf )
}
2023-10-23 18:14:40 +00:00
. max ( penalty_free_zone ( hf ) )
2023-10-02 20:07:11 +00:00
}
}
fn calculate_effective_median_block_weight (
hf : & HardFork ,
2023-10-28 23:39:22 +00:00
median_short_term_weight : usize ,
median_long_term_weight : usize ,
2023-10-02 20:07:11 +00:00
) -> usize {
if hf . in_range ( & HardFork ::V1 , & HardFork ::V10 ) {
2023-10-28 23:39:22 +00:00
return median_short_term_weight . max ( penalty_free_zone ( hf ) ) ;
2023-10-02 20:07:11 +00:00
}
2023-10-28 23:39:22 +00:00
let long_term_median = median_long_term_weight . max ( PENALTY_FREE_ZONE_5 ) ;
let short_term_median = median_short_term_weight ;
2023-10-02 20:07:11 +00:00
let effective_median = if hf . in_range ( & HardFork ::V10 , & HardFork ::V15 ) {
min (
max ( PENALTY_FREE_ZONE_5 , short_term_median ) ,
50 * long_term_median ,
)
} else {
min (
max ( long_term_median , short_term_median ) ,
50 * long_term_median ,
)
} ;
effective_median . max ( penalty_free_zone ( hf ) )
2023-09-28 11:21:06 +00:00
}
2023-10-24 01:25:11 +00:00
pub fn calculate_block_long_term_weight (
2023-09-28 11:21:06 +00:00
hf : & HardFork ,
block_weight : usize ,
2023-10-24 01:25:11 +00:00
long_term_median : usize ,
2023-09-28 11:21:06 +00:00
) -> usize {
if hf . in_range ( & HardFork ::V1 , & HardFork ::V10 ) {
return block_weight ;
}
2023-10-24 01:25:11 +00:00
let long_term_median = max ( penalty_free_zone ( hf ) , long_term_median ) ;
2023-09-28 11:21:06 +00:00
let ( short_term_constraint , adjusted_block_weight ) =
if hf . in_range ( & HardFork ::V10 , & HardFork ::V15 ) {
let stc = long_term_median + long_term_median * 2 / 5 ;
( stc , block_weight )
} else {
let stc = long_term_median + long_term_median * 7 / 10 ;
( stc , max ( block_weight , long_term_median * 10 / 17 ) )
} ;
min ( short_term_constraint , adjusted_block_weight )
}
2023-10-02 20:07:11 +00:00
#[ instrument(name = " get_block_weights " , skip(database)) ]
2023-09-28 11:21:06 +00:00
async fn get_blocks_weight_in_range < D : Database + Clone > (
range : Range < u64 > ,
database : D ,
2023-10-02 20:07:11 +00:00
) -> Result < Vec < usize > , ConsensusError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " getting block weights. " ) ;
2023-10-22 16:27:37 +00:00
let DatabaseResponse ::BlockExtendedHeaderInRange ( ext_headers ) = database
. oneshot ( DatabaseRequest ::BlockExtendedHeaderInRange ( range ) )
2023-09-28 11:21:06 +00:00
. await ?
else {
2023-10-02 20:07:11 +00:00
panic! ( " Database sent incorrect response! " )
2023-09-28 11:21:06 +00:00
} ;
2023-10-22 16:27:37 +00:00
Ok ( ext_headers
. into_iter ( )
. map ( | info | info . block_weight )
. collect ( ) )
2023-09-28 11:21:06 +00:00
}
2023-10-03 21:10:31 +00:00
#[ instrument(name = " get_long_term_weights " , skip(database), level = " info " ) ]
2023-09-28 11:21:06 +00:00
async fn get_long_term_weight_in_range < D : Database + Clone > (
range : Range < u64 > ,
database : D ,
2023-10-02 20:07:11 +00:00
) -> Result < Vec < usize > , ConsensusError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " getting block long term weights. " ) ;
2023-10-22 16:27:37 +00:00
let DatabaseResponse ::BlockExtendedHeaderInRange ( ext_headers ) = database
. oneshot ( DatabaseRequest ::BlockExtendedHeaderInRange ( range ) )
2023-09-28 11:21:06 +00:00
. await ?
else {
2023-10-02 20:07:11 +00:00
panic! ( " Database sent incorrect response! " )
2023-09-28 11:21:06 +00:00
} ;
2023-10-22 16:27:37 +00:00
Ok ( ext_headers
2023-09-28 11:21:06 +00:00
. into_iter ( )
. map ( | info | info . long_term_weight )
. collect ( ) )
}