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 } ,
ops ::Range ,
} ;
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-07-29 00:13:08 +00:00
use cuprate_helper ::{ asynch ::rayon_spawn_async , num ::RollingMedian } ;
use cuprate_types ::{
2024-08-05 20:47:30 +00:00
blockchain ::{ BlockchainReadRequest , BlockchainResponse } ,
2024-07-29 00:13:08 +00:00
Chain ,
} ;
2024-01-08 01:26:44 +00:00
2024-10-16 23:17:58 +00:00
use crate ::{ ContextCacheError , Database , HardFork } ;
2023-09-28 11:21:06 +00:00
2024-05-31 00:52:12 +00:00
/// The short term block weight window.
2024-10-16 23:17:58 +00:00
pub const SHORT_TERM_WINDOW : usize = 100 ;
2024-05-31 00:52:12 +00:00
/// The long term block weight window.
2024-10-16 23:17:58 +00:00
pub const LONG_TERM_WINDOW : usize = 100000 ;
2023-09-28 11:21:06 +00:00
2023-10-15 19:35:33 +00:00
/// Configuration for the block weight cache.
///
2024-07-29 00:13:08 +00:00
#[ derive(Debug, Clone, Copy, Eq, PartialEq) ]
2023-10-15 19:35:33 +00:00
pub struct BlockWeightsCacheConfig {
2024-08-06 23:48:53 +00:00
short_term_window : usize ,
long_term_window : usize ,
2023-10-15 19:35:33 +00:00
}
impl BlockWeightsCacheConfig {
2024-05-31 00:52:12 +00:00
/// Creates a new [`BlockWeightsCacheConfig`]
2024-09-21 00:32:03 +00:00
pub const fn new ( short_term_window : usize , long_term_window : usize ) -> Self {
Self {
2023-10-15 19:35:33 +00:00
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).
2024-09-21 00:32:03 +00:00
pub const fn main_net ( ) -> Self {
Self {
2023-10-15 19:35:33 +00:00
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.
2024-07-29 00:13:08 +00:00
#[ derive(Debug, Clone, Eq, PartialEq) ]
2023-09-28 11:21:06 +00:00
pub struct BlockWeightsCache {
2024-05-31 00:52:12 +00:00
/// The short term block weights.
2024-07-29 00:13:08 +00:00
short_term_block_weights : RollingMedian < usize > ,
2024-05-31 00:52:12 +00:00
/// The long term block weights.
2024-07-29 00:13:08 +00:00
long_term_weights : RollingMedian < usize > ,
2023-11-08 00:28:15 +00:00
2023-09-28 11:21:06 +00:00
/// The height of the top block.
2024-08-06 23:48:53 +00:00
pub ( crate ) tip_height : usize ,
2023-10-15 19:35:33 +00:00
2024-07-29 00:13:08 +00:00
pub ( crate ) 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 > (
2024-08-06 23:48:53 +00:00
chain_height : usize ,
2023-10-15 19:35:33 +00:00
config : BlockWeightsCacheConfig ,
2023-09-28 11:21:06 +00:00
database : D ,
2024-07-29 00:13:08 +00:00
chain : Chain ,
2024-10-16 23:17:58 +00:00
) -> Result < Self , ContextCacheError > {
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 ( ) ,
2024-07-29 00:13:08 +00:00
chain ,
2023-09-28 11:21:06 +00:00
)
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 ,
2024-07-29 00:13:08 +00:00
chain ,
2023-09-28 11:21:06 +00:00
)
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
2024-09-21 00:32:03 +00:00
Ok ( Self {
2024-07-29 00:13:08 +00:00
short_term_block_weights : rayon_spawn_async ( move | | {
2024-08-06 23:48:53 +00:00
RollingMedian ::from_vec ( short_term_block_weights , config . short_term_window )
2023-11-08 00:28:15 +00:00
} )
. await ,
2024-07-29 00:13:08 +00:00
long_term_weights : rayon_spawn_async ( move | | {
2024-08-06 23:48:53 +00:00
RollingMedian ::from_vec ( long_term_weights , config . long_term_window )
2023-11-08 00:28:15 +00:00
} )
. 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
} )
}
2024-07-29 00:13:08 +00:00
/// Pop some blocks from the top of the cache.
///
/// The cache will be returned to the state it would have been in `numb_blocks` ago.
#[ instrument(name = " pop_blocks_weight_cache " , skip_all, fields(numb_blocks = numb_blocks)) ]
pub async fn pop_blocks_main_chain < D : Database + Clone > (
& mut self ,
2024-08-06 23:48:53 +00:00
numb_blocks : usize ,
2024-07-29 00:13:08 +00:00
database : D ,
2024-10-16 23:17:58 +00:00
) -> Result < ( ) , ContextCacheError > {
2024-08-06 23:48:53 +00:00
if self . long_term_weights . window_len ( ) < = numb_blocks {
2024-07-29 00:13:08 +00:00
// More blocks to pop than we have in the cache, so just restart a new cache.
* self = Self ::init_from_chain_height (
self . tip_height - numb_blocks + 1 ,
self . config ,
database ,
Chain ::Main ,
)
. await ? ;
return Ok ( ( ) ) ;
}
let chain_height = self . tip_height + 1 ;
let new_long_term_start_height = chain_height
. saturating_sub ( self . config . long_term_window )
. saturating_sub ( numb_blocks ) ;
let old_long_term_weights = get_long_term_weight_in_range (
new_long_term_start_height
// current_chain_height - self.long_term_weights.len() blocks are already in the cache.
2024-08-06 23:48:53 +00:00
.. ( chain_height - self . long_term_weights . window_len ( ) ) ,
2024-07-29 00:13:08 +00:00
database . clone ( ) ,
Chain ::Main ,
)
. await ? ;
let new_short_term_start_height = chain_height
. saturating_sub ( self . config . short_term_window )
. saturating_sub ( numb_blocks ) ;
let old_short_term_weights = get_blocks_weight_in_range (
new_short_term_start_height
// current_chain_height - self.long_term_weights.len() blocks are already in the cache.
2024-08-06 23:48:53 +00:00
.. ( chain_height - self . short_term_block_weights . window_len ( ) ) ,
2024-07-29 00:13:08 +00:00
database ,
2024-08-06 23:48:53 +00:00
Chain ::Main ,
2024-07-29 00:13:08 +00:00
)
2024-08-06 23:48:53 +00:00
. await ? ;
2024-07-29 00:13:08 +00:00
for _ in 0 .. numb_blocks {
self . short_term_block_weights . pop_back ( ) ;
self . long_term_weights . pop_back ( ) ;
}
self . long_term_weights . append_front ( old_long_term_weights ) ;
self . short_term_block_weights
. append_front ( old_short_term_weights ) ;
self . tip_height - = numb_blocks ;
Ok ( ( ) )
}
2023-10-02 20:07:11 +00:00
/// Add a new block to the cache.
///
2024-09-21 00:32:03 +00:00
/// The `block_height` **MUST** be one more than the last height the cache has
2023-10-02 20:07:11 +00:00
/// seen.
2024-08-06 23:48:53 +00:00
pub fn new_block ( & mut self , block_height : usize , 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-07-29 00:13:08 +00:00
self . long_term_weights . push ( long_term_weight ) ;
2023-11-08 00:28:15 +00:00
2024-07-29 00:13:08 +00:00
self . short_term_block_weights . push ( block_weight ) ;
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 {
2024-07-29 00:13:08 +00:00
self . long_term_weights . median ( )
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 {
2024-07-29 00:13:08 +00:00
self . short_term_block_weights . median ( )
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.
///
2024-09-21 00:32:03 +00:00
/// 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 {
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.
///
2024-09-21 00:32:03 +00:00
/// <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 < 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 (
2024-09-21 00:32:03 +00:00
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 {
2024-09-21 00:32:03 +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 ;
2024-09-21 00:32:03 +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.
2024-10-16 23:17:58 +00:00
pub fn calculate_block_long_term_weight (
2024-09-21 00:32:03 +00:00
hf : HardFork ,
2023-09-28 11:21:06 +00:00
block_weight : usize ,
2023-10-24 01:25:11 +00:00
long_term_median : usize ,
2023-09-28 11:21:06 +00:00
) -> usize {
2024-09-21 00:32:03 +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 ) =
2024-09-21 00:32:03 +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 > (
2024-08-06 23:48:53 +00:00
range : Range < usize > ,
2023-09-28 11:21:06 +00:00
database : D ,
2024-07-29 00:13:08 +00:00
chain : Chain ,
2024-10-16 23:17:58 +00:00
) -> Result < Vec < usize > , ContextCacheError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " getting block weights. " ) ;
2024-08-05 20:47:30 +00:00
let BlockchainResponse ::BlockExtendedHeaderInRange ( ext_headers ) = database
. oneshot ( BlockchainReadRequest ::BlockExtendedHeaderInRange (
range , chain ,
) )
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 > (
2024-08-06 23:48:53 +00:00
range : Range < usize > ,
2023-09-28 11:21:06 +00:00
database : D ,
2024-07-29 00:13:08 +00:00
chain : Chain ,
2024-10-16 23:17:58 +00:00
) -> Result < Vec < usize > , ContextCacheError > {
2023-10-03 21:10:31 +00:00
tracing ::info! ( " getting block long term weights. " ) ;
2024-08-05 20:47:30 +00:00
let BlockchainResponse ::BlockExtendedHeaderInRange ( ext_headers ) = database
. oneshot ( BlockchainReadRequest ::BlockExtendedHeaderInRange (
range , chain ,
) )
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 ( ) )
}