cuprate_consensus_context/
weight.rs1use std::{
10 cmp::{max, min},
11 ops::Range,
12};
13
14use tower::ServiceExt;
15use tracing::instrument;
16
17use cuprate_consensus_rules::blocks::{penalty_free_zone, PENALTY_FREE_ZONE_5};
18use cuprate_helper::{asynch::rayon_spawn_async, num::RollingMedian};
19use cuprate_types::{
20 blockchain::{BlockchainReadRequest, BlockchainResponse},
21 Chain,
22};
23
24use crate::{ContextCacheError, Database, HardFork};
25
26pub const SHORT_TERM_WINDOW: usize = 100;
28pub const LONG_TERM_WINDOW: usize = 100000;
30
31#[derive(Debug, Clone, Copy, Eq, PartialEq)]
34pub struct BlockWeightsCacheConfig {
35 short_term_window: usize,
36 long_term_window: usize,
37}
38
39impl BlockWeightsCacheConfig {
40 pub const fn new(short_term_window: usize, long_term_window: usize) -> Self {
42 Self {
43 short_term_window,
44 long_term_window,
45 }
46 }
47
48 pub const fn main_net() -> Self {
50 Self {
51 short_term_window: SHORT_TERM_WINDOW,
52 long_term_window: LONG_TERM_WINDOW,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Eq, PartialEq)]
63pub struct BlockWeightsCache {
64 short_term_block_weights: RollingMedian<usize>,
66 long_term_weights: RollingMedian<usize>,
68
69 pub(crate) tip_height: usize,
71
72 pub(crate) config: BlockWeightsCacheConfig,
73}
74
75impl BlockWeightsCache {
76 #[instrument(name = "init_weight_cache", level = "info", skip(database, config))]
78 pub async fn init_from_chain_height<D: Database + Clone>(
79 chain_height: usize,
80 config: BlockWeightsCacheConfig,
81 database: D,
82 chain: Chain,
83 ) -> Result<Self, ContextCacheError> {
84 tracing::info!("Initializing weight cache this may take a while.");
85
86 let long_term_weights = get_long_term_weight_in_range(
87 chain_height.saturating_sub(config.long_term_window)..chain_height,
88 database.clone(),
89 chain,
90 )
91 .await?;
92
93 let short_term_block_weights = get_blocks_weight_in_range(
94 chain_height.saturating_sub(config.short_term_window)..chain_height,
95 database,
96 chain,
97 )
98 .await?;
99
100 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());
101
102 Ok(Self {
103 short_term_block_weights: rayon_spawn_async(move || {
104 RollingMedian::from_vec(short_term_block_weights, config.short_term_window)
105 })
106 .await,
107 long_term_weights: rayon_spawn_async(move || {
108 RollingMedian::from_vec(long_term_weights, config.long_term_window)
109 })
110 .await,
111 tip_height: chain_height - 1,
112 config,
113 })
114 }
115
116 #[instrument(name = "pop_blocks_weight_cache", skip_all, fields(numb_blocks = numb_blocks))]
120 pub async fn pop_blocks_main_chain<D: Database + Clone>(
121 &mut self,
122 numb_blocks: usize,
123 database: D,
124 ) -> Result<(), ContextCacheError> {
125 if self.long_term_weights.window_len() <= numb_blocks {
126 *self = Self::init_from_chain_height(
128 self.tip_height - numb_blocks + 1,
129 self.config,
130 database,
131 Chain::Main,
132 )
133 .await?;
134
135 return Ok(());
136 }
137
138 let chain_height = self.tip_height + 1;
139
140 let new_long_term_start_height =
141 chain_height.saturating_sub(self.config.long_term_window + numb_blocks);
142
143 let old_long_term_weights = get_long_term_weight_in_range(
144 new_long_term_start_height..
145 (chain_height - self.long_term_weights.window_len()),
148 database.clone(),
149 Chain::Main,
150 )
151 .await?;
152
153 let new_short_term_start_height =
154 chain_height.saturating_sub(self.config.short_term_window + numb_blocks);
155
156 let old_short_term_weights = get_blocks_weight_in_range(
157 new_short_term_start_height
158 ..(
159 min(
161 chain_height - self.short_term_block_weights.window_len(),
163 chain_height - numb_blocks,
165 )
166 ),
167 database,
168 Chain::Main,
169 )
170 .await?;
171
172 for _ in 0..numb_blocks {
173 self.short_term_block_weights.pop_back();
174 self.long_term_weights.pop_back();
175 }
176
177 self.long_term_weights.append_front(old_long_term_weights);
178 self.short_term_block_weights
179 .append_front(old_short_term_weights);
180 self.tip_height -= numb_blocks;
181
182 Ok(())
183 }
184
185 pub fn new_block(&mut self, block_height: usize, block_weight: usize, long_term_weight: usize) {
190 assert_eq!(self.tip_height + 1, block_height);
191 self.tip_height += 1;
192 tracing::debug!(
193 "Adding new block's {} weights to block cache, weight: {}, long term weight: {}",
194 self.tip_height,
195 block_weight,
196 long_term_weight
197 );
198
199 self.long_term_weights.push(long_term_weight);
200
201 self.short_term_block_weights.push(block_weight);
202 }
203
204 pub fn median_long_term_weight(&self) -> usize {
206 self.long_term_weights.median()
207 }
208
209 pub fn median_short_term_weight(&self) -> usize {
211 self.short_term_block_weights.median()
212 }
213
214 pub fn effective_median_block_weight(&self, hf: HardFork) -> usize {
219 calculate_effective_median_block_weight(
220 hf,
221 self.median_short_term_weight(),
222 self.median_long_term_weight(),
223 )
224 }
225
226 pub fn median_for_block_reward(&self, hf: HardFork) -> usize {
230 if hf < HardFork::V12 {
231 self.median_short_term_weight()
232 } else {
233 self.effective_median_block_weight(hf)
234 }
235 .max(penalty_free_zone(hf))
236 }
237}
238
239fn calculate_effective_median_block_weight(
241 hf: HardFork,
242 median_short_term_weight: usize,
243 median_long_term_weight: usize,
244) -> usize {
245 if hf < HardFork::V10 {
246 return median_short_term_weight.max(penalty_free_zone(hf));
247 }
248
249 let long_term_median = median_long_term_weight.max(PENALTY_FREE_ZONE_5);
250 let short_term_median = median_short_term_weight;
251 let effective_median = if hf >= HardFork::V10 && hf < HardFork::V15 {
252 min(
253 max(PENALTY_FREE_ZONE_5, short_term_median),
254 50 * long_term_median,
255 )
256 } else {
257 min(
258 max(long_term_median, short_term_median),
259 50 * long_term_median,
260 )
261 };
262
263 effective_median.max(penalty_free_zone(hf))
264}
265
266pub fn calculate_block_long_term_weight(
268 hf: HardFork,
269 block_weight: usize,
270 long_term_median: usize,
271) -> usize {
272 if hf < HardFork::V10 {
273 return block_weight;
274 }
275
276 let long_term_median = max(penalty_free_zone(hf), long_term_median);
277
278 let (short_term_constraint, adjusted_block_weight) =
279 if hf >= HardFork::V10 && hf < HardFork::V15 {
280 let stc = long_term_median + long_term_median * 2 / 5;
281 (stc, block_weight)
282 } else {
283 let stc = long_term_median + long_term_median * 7 / 10;
284 (stc, max(block_weight, long_term_median * 10 / 17))
285 };
286
287 min(short_term_constraint, adjusted_block_weight)
288}
289
290#[instrument(name = "get_block_weights", skip(database))]
292async fn get_blocks_weight_in_range<D: Database + Clone>(
293 range: Range<usize>,
294 database: D,
295 chain: Chain,
296) -> Result<Vec<usize>, ContextCacheError> {
297 tracing::info!("getting block weights.");
298
299 let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
300 .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
301 range, chain,
302 ))
303 .await?
304 else {
305 panic!("Database sent incorrect response!")
306 };
307
308 Ok(ext_headers
309 .into_iter()
310 .map(|info| info.block_weight)
311 .collect())
312}
313
314#[instrument(name = "get_long_term_weights", skip(database), level = "info")]
316async fn get_long_term_weight_in_range<D: Database + Clone>(
317 range: Range<usize>,
318 database: D,
319 chain: Chain,
320) -> Result<Vec<usize>, ContextCacheError> {
321 tracing::info!("getting block long term weights.");
322
323 let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
324 .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
325 range, chain,
326 ))
327 .await?
328 else {
329 panic!("Database sent incorrect response!")
330 };
331
332 Ok(ext_headers
333 .into_iter()
334 .map(|info| info.long_term_weight)
335 .collect())
336}