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
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 ;
2024-05-31 00:52:12 +00:00
use cuprate_consensus_rules ::blocks ::{ penalty_free_zone , PENALTY_FREE_ZONE_5 } ;
2024-01-22 01:56:34 +00:00
use cuprate_helper ::{ asynch ::rayon_spawn_async , num ::median } ;
2024-06-04 17:19:35 +00:00
use cuprate_types ::blockchain ::{ BCReadRequest , BCResponse } ;
2024-01-08 01:26:44 +00:00
2024-06-04 17:19:35 +00:00
use crate ::{ Database , ExtendedConsensusError , HardFork } ;
2023-09-28 11:21:06 +00:00
2024-05-31 00:52:12 +00:00
/// The short term block weight window.
2023-09-28 11:21:06 +00:00
const SHORT_TERM_WINDOW : u64 = 100 ;
2024-05-31 00:52:12 +00:00
/// The long term block weight window.
2023-09-28 11:21:06 +00:00
const LONG_TERM_WINDOW : u64 = 100000 ;
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 {
2024-05-31 00:52:12 +00:00
/// Creates a new [`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 ,
}
}
2024-05-31 00:52:12 +00:00
/// Returns the [`BlockWeightsCacheConfig`] for all networks (They are all the same as mainnet).
2023-10-15 19:35:33 +00:00
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 {
2024-05-31 00:52:12 +00:00
/// The short term block weights.
2023-10-02 20:07:11 +00:00
short_term_block_weights : VecDeque < usize > ,
2024-05-31 00:52:12 +00:00
/// The long term block weights.
2023-10-24 01:25:11 +00:00
long_term_weights : VecDeque < usize > ,
2023-11-08 00:28:15 +00:00
/// The short term block weights sorted so we don't have to sort them every time we need
/// the median.
cached_sorted_long_term_weights : Vec < usize > ,
/// The long term block weights sorted so we don't have to sort them every time we need
/// the median.
cached_sorted_short_term_weights : Vec < 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
2024-05-31 00:52:12 +00:00
/// The block weight config.
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 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-12-16 23:03:02 +00:00
) -> Result < Self , ExtendedConsensusError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " Initializing weight cache this may take a while. " ) ;
2023-11-08 00:28:15 +00:00
let long_term_weights = 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-11-08 00:28:15 +00:00
. await ? ;
2023-09-28 11:21:06 +00:00
2023-11-08 00:28:15 +00:00
let short_term_block_weights = 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-11-08 00:28:15 +00:00
. await ? ;
2023-10-02 20:07:11 +00:00
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
2023-11-08 00:28:15 +00:00
let mut cloned_short_term_weights = short_term_block_weights . clone ( ) ;
let mut cloned_long_term_weights = long_term_weights . clone ( ) ;
2023-09-28 11:21:06 +00:00
Ok ( BlockWeightsCache {
2023-11-08 00:28:15 +00:00
short_term_block_weights : short_term_block_weights . into ( ) ,
long_term_weights : long_term_weights . into ( ) ,
cached_sorted_long_term_weights : rayon_spawn_async ( | | {
cloned_long_term_weights . par_sort_unstable ( ) ;
cloned_long_term_weights
} )
. await ,
cached_sorted_short_term_weights : rayon_spawn_async ( | | {
cloned_short_term_weights . par_sort_unstable ( ) ;
cloned_short_term_weights
} )
. await ,
2023-09-28 11:21:06 +00:00
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
) ;
2024-05-31 00:52:12 +00:00
// add the new block to the `long_term_weights` list and the sorted `cached_sorted_long_term_weights` list.
2023-10-24 01:25:11 +00:00
self . long_term_weights . push_back ( long_term_weight ) ;
2023-11-08 00:28:15 +00:00
match self
. cached_sorted_long_term_weights
. binary_search ( & long_term_weight )
{
Ok ( idx ) | Err ( idx ) = > self
. cached_sorted_long_term_weights
. insert ( idx , long_term_weight ) ,
}
2024-05-31 00:52:12 +00:00
// If the list now has too many entries remove the oldest.
2023-10-23 18:14:40 +00:00
if u64 ::try_from ( self . long_term_weights . len ( ) ) . unwrap ( ) > self . config . long_term_window {
2023-11-08 00:28:15 +00:00
let val = self
. long_term_weights
. pop_front ( )
. expect ( " long term window can't be negative " ) ;
match self . cached_sorted_long_term_weights . binary_search ( & val ) {
2024-01-05 22:36:47 +00:00
Ok ( idx ) = > self . cached_sorted_long_term_weights . remove ( idx ) ,
Err ( _ ) = > panic! ( " Long term cache has incorrect values! " ) ,
2023-11-08 00:28:15 +00:00
} ;
2023-10-02 20:07:11 +00:00
}
2024-05-31 00:52:12 +00:00
// add the block to the short_term_block_weights and the sorted cached_sorted_short_term_weights list.
2023-10-02 20:07:11 +00:00
self . short_term_block_weights . push_back ( block_weight ) ;
2023-11-08 00:28:15 +00:00
match self
. cached_sorted_short_term_weights
2024-01-05 22:36:47 +00:00
. binary_search ( & block_weight )
2023-11-08 00:28:15 +00:00
{
Ok ( idx ) | Err ( idx ) = > self
. cached_sorted_short_term_weights
2024-01-05 22:36:47 +00:00
. insert ( idx , block_weight ) ,
2023-11-08 00:28:15 +00:00
}
2024-05-31 00:52:12 +00:00
// If there are now too many entries remove the oldest.
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-11-08 00:28:15 +00:00
let val = self
. short_term_block_weights
. pop_front ( )
. expect ( " short term window can't be negative " ) ;
match self . cached_sorted_short_term_weights . binary_search ( & val ) {
2024-01-05 22:36:47 +00:00
Ok ( idx ) = > self . cached_sorted_short_term_weights . remove ( idx ) ,
Err ( _ ) = > panic! ( " Short term cache has incorrect values " ) ,
2023-11-08 00:28:15 +00:00
} ;
2023-10-02 20:07:11 +00:00
}
2023-11-08 00:28:15 +00:00
debug_assert_eq! (
self . cached_sorted_long_term_weights . len ( ) ,
self . long_term_weights . len ( )
) ;
debug_assert_eq! (
self . cached_sorted_short_term_weights . len ( ) ,
self . short_term_block_weights . len ( )
) ;
2023-10-02 20:07:11 +00:00
}
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.
2023-11-08 00:28:15 +00:00
pub fn median_long_term_weight ( & self ) -> usize {
median ( & self . cached_sorted_long_term_weights )
2023-10-22 16:27:37 +00:00
}
2024-05-31 00:52:12 +00:00
/// Returns the median weight over the last [`SHORT_TERM_WINDOW`] blocks, or custom amount of blocks in the config.
2023-10-28 23:39:22 +00:00
pub fn median_short_term_weight ( & self ) -> usize {
2023-11-08 00:28:15 +00:00
median ( & self . cached_sorted_short_term_weights )
2023-10-28 23:39:22 +00:00
}
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
2023-11-08 00:28:15 +00:00
pub fn effective_median_block_weight ( & self , hf : & HardFork ) -> usize {
2023-10-02 20:07:11 +00:00
calculate_effective_median_block_weight (
hf ,
2023-10-28 23:39:22 +00:00
self . median_short_term_weight ( ) ,
2023-11-08 00:28:15 +00:00
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
2023-11-08 00:28:15 +00:00
pub fn median_for_block_reward ( & self , hf : & HardFork ) -> usize {
2023-12-16 23:03:02 +00:00
if hf < & HardFork ::V12 {
2023-10-28 23:39:22 +00:00
self . median_short_term_weight ( )
2023-10-15 19:35:33 +00:00
} else {
2023-11-08 00:28:15 +00:00
self . effective_median_block_weight ( hf )
2023-10-15 19:35:33 +00:00
}
2023-10-23 18:14:40 +00:00
. max ( penalty_free_zone ( hf ) )
2023-10-02 20:07:11 +00:00
}
}
2024-05-31 00:52:12 +00:00
/// Calculates the effective median with the long term and short term median.
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 {
2023-12-16 23:03:02 +00:00
if hf < & 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-12-16 23:03:02 +00:00
let effective_median = if hf > = & HardFork ::V10 & & hf < & HardFork ::V15 {
2023-10-02 20:07:11 +00:00
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
}
2024-05-31 00:52:12 +00:00
/// Calculates a blocks long term weight.
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 {
2023-12-16 23:03:02 +00:00
if hf < & HardFork ::V10 {
2023-09-28 11:21:06 +00:00
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 ) =
2023-12-16 23:03:02 +00:00
if hf > = & HardFork ::V10 & & hf < & HardFork ::V15 {
2023-09-28 11:21:06 +00:00
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 )
}
2024-05-31 00:52:12 +00:00
/// Gets the block weights from the blocks with heights in the range provided.
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-12-16 23:03:02 +00:00
) -> Result < Vec < usize > , ExtendedConsensusError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " getting block weights. " ) ;
2024-06-04 17:19:35 +00:00
let BCResponse ::BlockExtendedHeaderInRange ( ext_headers ) = database
. oneshot ( BCReadRequest ::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
}
2024-05-31 00:52:12 +00:00
/// Gets the block long term weights from the blocks with heights in the range provided.
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-12-16 23:03:02 +00:00
) -> Result < Vec < usize > , ExtendedConsensusError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " getting block long term weights. " ) ;
2024-06-04 17:19:35 +00:00
let BCResponse ::BlockExtendedHeaderInRange ( ext_headers ) = database
. oneshot ( BCReadRequest ::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 ( ) )
}