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 = chain_height
141 .saturating_sub(self.config.long_term_window)
142 .saturating_sub(numb_blocks);
143
144 let old_long_term_weights = get_long_term_weight_in_range(
145 new_long_term_start_height
146 ..(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 = chain_height
154 .saturating_sub(self.config.short_term_window)
155 .saturating_sub(numb_blocks);
156
157 let old_short_term_weights = get_blocks_weight_in_range(
158 new_short_term_start_height
159 ..(chain_height - self.short_term_block_weights.window_len()),
161 database,
162 Chain::Main,
163 )
164 .await?;
165
166 for _ in 0..numb_blocks {
167 self.short_term_block_weights.pop_back();
168 self.long_term_weights.pop_back();
169 }
170
171 self.long_term_weights.append_front(old_long_term_weights);
172 self.short_term_block_weights
173 .append_front(old_short_term_weights);
174 self.tip_height -= numb_blocks;
175
176 Ok(())
177 }
178
179 pub fn new_block(&mut self, block_height: usize, block_weight: usize, long_term_weight: usize) {
184 assert_eq!(self.tip_height + 1, block_height);
185 self.tip_height += 1;
186 tracing::debug!(
187 "Adding new block's {} weights to block cache, weight: {}, long term weight: {}",
188 self.tip_height,
189 block_weight,
190 long_term_weight
191 );
192
193 self.long_term_weights.push(long_term_weight);
194
195 self.short_term_block_weights.push(block_weight);
196 }
197
198 pub fn median_long_term_weight(&self) -> usize {
200 self.long_term_weights.median()
201 }
202
203 pub fn median_short_term_weight(&self) -> usize {
205 self.short_term_block_weights.median()
206 }
207
208 pub fn effective_median_block_weight(&self, hf: HardFork) -> usize {
213 calculate_effective_median_block_weight(
214 hf,
215 self.median_short_term_weight(),
216 self.median_long_term_weight(),
217 )
218 }
219
220 pub fn median_for_block_reward(&self, hf: HardFork) -> usize {
224 if hf < HardFork::V12 {
225 self.median_short_term_weight()
226 } else {
227 self.effective_median_block_weight(hf)
228 }
229 .max(penalty_free_zone(hf))
230 }
231}
232
233fn calculate_effective_median_block_weight(
235 hf: HardFork,
236 median_short_term_weight: usize,
237 median_long_term_weight: usize,
238) -> usize {
239 if hf < HardFork::V10 {
240 return median_short_term_weight.max(penalty_free_zone(hf));
241 }
242
243 let long_term_median = median_long_term_weight.max(PENALTY_FREE_ZONE_5);
244 let short_term_median = median_short_term_weight;
245 let effective_median = if hf >= HardFork::V10 && hf < HardFork::V15 {
246 min(
247 max(PENALTY_FREE_ZONE_5, short_term_median),
248 50 * long_term_median,
249 )
250 } else {
251 min(
252 max(long_term_median, short_term_median),
253 50 * long_term_median,
254 )
255 };
256
257 effective_median.max(penalty_free_zone(hf))
258}
259
260pub fn calculate_block_long_term_weight(
262 hf: HardFork,
263 block_weight: usize,
264 long_term_median: usize,
265) -> usize {
266 if hf < HardFork::V10 {
267 return block_weight;
268 }
269
270 let long_term_median = max(penalty_free_zone(hf), long_term_median);
271
272 let (short_term_constraint, adjusted_block_weight) =
273 if hf >= HardFork::V10 && hf < HardFork::V15 {
274 let stc = long_term_median + long_term_median * 2 / 5;
275 (stc, block_weight)
276 } else {
277 let stc = long_term_median + long_term_median * 7 / 10;
278 (stc, max(block_weight, long_term_median * 10 / 17))
279 };
280
281 min(short_term_constraint, adjusted_block_weight)
282}
283
284#[instrument(name = "get_block_weights", skip(database))]
286async fn get_blocks_weight_in_range<D: Database + Clone>(
287 range: Range<usize>,
288 database: D,
289 chain: Chain,
290) -> Result<Vec<usize>, ContextCacheError> {
291 tracing::info!("getting block weights.");
292
293 let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
294 .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
295 range, chain,
296 ))
297 .await?
298 else {
299 panic!("Database sent incorrect response!")
300 };
301
302 Ok(ext_headers
303 .into_iter()
304 .map(|info| info.block_weight)
305 .collect())
306}
307
308#[instrument(name = "get_long_term_weights", skip(database), level = "info")]
310async fn get_long_term_weight_in_range<D: Database + Clone>(
311 range: Range<usize>,
312 database: D,
313 chain: Chain,
314) -> Result<Vec<usize>, ContextCacheError> {
315 tracing::info!("getting block long term weights.");
316
317 let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
318 .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
319 range, chain,
320 ))
321 .await?
322 else {
323 panic!("Database sent incorrect response!")
324 };
325
326 Ok(ext_headers
327 .into_iter()
328 .map(|info| info.long_term_weight)
329 .collect())
330}