cfxcore/consensus/consensus_inner/
confirmation_meter.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::consensus::{
6    consensus_inner::{NULL, NULLU64},
7    ConsensusGraphInner,
8};
9use cfx_parameters::{
10    consensus::DEFERRED_STATE_EPOCH_COUNT, consensus_internal::*,
11};
12use cfx_types::H256;
13use parking_lot::RwLock;
14use std::{cmp::max, collections::VecDeque, convert::TryFrom};
15
16pub struct TotalWeightInPastMovingDelta {
17    pub old: i128,
18    pub cur: i128,
19    pub delta: i128,
20}
21
22pub struct FinalityManager {
23    pub lowest_epoch_num: u64,
24    pub risks_less_than: VecDeque<f64>,
25}
26
27struct ConfirmationMeterInner {
28    total_weight_in_past_2d: TotalWeightInPastMovingDelta,
29    finality_manager: FinalityManager,
30}
31
32impl ConfirmationMeterInner {
33    pub fn new() -> Self {
34        Self {
35            total_weight_in_past_2d: TotalWeightInPastMovingDelta {
36                old: 0,
37                cur: 0,
38                delta: 0,
39            },
40            finality_manager: FinalityManager {
41                lowest_epoch_num: 0,
42                risks_less_than: VecDeque::new(),
43            },
44        }
45    }
46}
47
48/// `ConfirmationMeter` computes an approximate *local view* confirmation risk
49/// given the current blockchain state. Local view means that the meter assumes
50/// a potential block propagation delay and assumes a worst case scenario of
51/// what this delay could do.
52///
53/// The meter serves two purposes. First, it allows the underlying storage layer
54/// to determine whether it is *relatively safe* to discard previous snapshots.
55/// Snapshot consumes a lot of disk space and it is ideal to discard old ones.
56/// Second, it enables the consensus layer to provide an interface to query the
57/// confirmation status of a block/transaction.
58pub struct ConfirmationMeter {
59    inner: RwLock<ConfirmationMeterInner>,
60}
61
62impl ConfirmationMeter {
63    pub fn new() -> Self {
64        Self {
65            inner: RwLock::new(ConfirmationMeterInner::new()),
66        }
67    }
68
69    pub fn clear(&self) {
70        let mut inner = self.inner.write();
71        *inner = ConfirmationMeterInner::new();
72    }
73
74    /// This is the function that should be invoked every 2 *
75    /// BLOCK_PROPAGATION_DELAY by the synchronization layer to measure the
76    /// weight of generated blocks in 2d
77    pub fn update_total_weight_delta_heartbeat(&self) {
78        let mut inner = self.inner.write();
79        let total_weight = &mut inner.total_weight_in_past_2d;
80        total_weight.delta = total_weight.cur - total_weight.old;
81        total_weight.old = total_weight.cur;
82    }
83
84    /// The `ConsensusGraph` calls this function for every inserted and
85    /// activated block to accumulate the total weight value
86    pub fn aggregate_total_weight_in_past(&self, weight: i128) {
87        let mut inner = self.inner.write();
88        let total_weight = &mut inner.total_weight_in_past_2d;
89        total_weight.cur += weight;
90    }
91
92    /// The `ConsensusGraph` invokes this function when making a checkpoint. The
93    /// confirmation meter needs to aware of the genesis change and make
94    /// adjustment accordingly.
95    pub fn reset_for_checkpoint(&self, total_weight: i128, stable_height: u64) {
96        let mut inner = self.inner.write();
97        let change = inner.total_weight_in_past_2d.cur - total_weight;
98        inner.total_weight_in_past_2d.cur = total_weight;
99        inner.total_weight_in_past_2d.old -= change;
100
101        if stable_height > inner.finality_manager.lowest_epoch_num {
102            let gap = stable_height - inner.finality_manager.lowest_epoch_num;
103            for _i in 0..gap {
104                inner.finality_manager.risks_less_than.pop_front();
105            }
106            inner.finality_manager.lowest_epoch_num = stable_height;
107        }
108    }
109
110    pub fn get_confirmed_epoch_num(&self) -> u64 {
111        let x = self.inner.read().finality_manager.lowest_epoch_num;
112        if x > 0 {
113            x - 1
114        } else {
115            0
116        }
117    }
118
119    /// Query the confirmation hash of a specific block.
120    pub fn confirmation_risk_by_hash(
121        &self, g_inner: &ConsensusGraphInner, hash: H256,
122    ) -> Option<f64> {
123        if hash == g_inner.data_man.true_genesis.hash() {
124            return Some(CONFIRMATION_METER_MIN_MAINTAINED_RISK);
125        }
126        let index = match g_inner.hash_to_arena_indices.get(&hash) {
127            Some(i) => *i,
128            None => {
129                // The block is not in memory, check if it's confirmed before.
130                return match g_inner
131                    .data_man
132                    .block_execution_result_by_hash_from_db(&hash)
133                {
134                    // It's garbage collected because of checkpoint, but it
135                    // is executed before checkpoint, so
136                    // is definitely confirmed.
137                    Some(_) => Some(CONFIRMATION_METER_MIN_MAINTAINED_RISK),
138                    // The block has not entered consensus or it's skipped
139                    // in execution, either not-in-same-era
140                    // or not in the epoch set bound.
141                    // FIXME: Skipped blocks' order are actually confirmed.
142                    None => None,
143                };
144            }
145        };
146        let epoch_num = g_inner.arena[index].data.epoch_number;
147        if epoch_num == NULLU64 {
148            // The block is in the anticone of cur era genesis or its not
149            // included in any epoch on the pivot chain yet.
150            // FIXME: Its order is confirmed if it's in cur_era_genesis
151            // anticone.
152            return None;
153        }
154
155        if epoch_num == 0 {
156            return Some(0.0);
157        }
158
159        let finality = &self.inner.read().finality_manager;
160
161        if epoch_num < finality.lowest_epoch_num {
162            return Some(CONFIRMATION_METER_MIN_MAINTAINED_RISK);
163        }
164
165        let idx = (epoch_num - finality.lowest_epoch_num) as usize;
166        if idx < finality.risks_less_than.len() {
167            let mut max_risk = 0.0;
168            for i in 0..idx + 1 {
169                let risk = *finality.risks_less_than.get(i).unwrap();
170                if max_risk < risk {
171                    max_risk = risk;
172                }
173            }
174            Some(max_risk)
175        } else {
176            Some(0.9)
177        }
178    }
179
180    fn confirmation_risk(
181        &self, g_inner: &ConsensusGraphInner, w_0: i128, w_4: i128,
182        epoch_num: u64,
183    ) -> f64 {
184        // Compute w_1
185        let idx = g_inner.get_pivot_block_arena_index(epoch_num);
186        let pivot_idx = g_inner.height_to_pivot_index(epoch_num);
187        let w_1 = g_inner.weight_tree.get(idx);
188
189        // Compute w_2
190        let parent = g_inner.arena[idx].parent;
191        assert!(parent != NULL);
192        let mut max_weight = 0;
193        for child in g_inner.arena[parent].children.iter() {
194            if *child == idx {
195                continue;
196            }
197
198            let child_weight = g_inner.weight_tree.get(*child);
199            if child_weight > max_weight {
200                max_weight = child_weight;
201            }
202        }
203        let w_2 = max_weight;
204
205        // Compute w_3
206        let w_3 = g_inner.pivot_chain_metadata[pivot_idx].past_weight;
207
208        // Compute d
209        let d = i128::try_from(g_inner.current_difficulty.low_u128()).unwrap();
210
211        // Compute n
212        let w_2_4 = w_2 + w_4;
213        let n = if w_1 >= w_2_4 { w_1 - w_2_4 } else { 0 };
214
215        let n = (n / d) + 1;
216
217        // Compute m
218        let m = if w_0 >= w_3 { w_0 - w_3 } else { 0 };
219
220        let m = m / d;
221
222        // debug!("Confirmation Risk: m {} n {} w_0 {}, w_1 {}, w_2 {}, w_3 {},
223        // w_4 {}, epoch_num {} genesis {}", m, n, w_0, w_1, w_2, w_3, w_4,
224        // epoch_num, g_inner.cur_era_genesis_block_arena_index);
225
226        // Compute risk
227        let m_n_diff = m as f64 - n as f64;
228        let mut risk = 0.9;
229        let threshold_1 = if 0.75 * m as f64 - 22.0 < 2250.0 {
230            0.75 * m as f64 - 22.0
231        } else {
232            2250.0
233        };
234        if m_n_diff >= threshold_1 {
235            return risk;
236        }
237        risk = 0.0001;
238        let threshold_2 = if 0.70 * m as f64 - 22.0 < 1500.0 {
239            0.70 * m as f64 - 22.0
240        } else {
241            1500.0
242        };
243        if m_n_diff >= threshold_2 {
244            return risk;
245        }
246        risk = 0.000001;
247        let threshold_3 = if 0.65 * m as f64 - 22.0 < 750.0 {
248            0.65 * m as f64
249        } else {
250            750.0
251        };
252        if m_n_diff >= threshold_3 {
253            return risk;
254        }
255        risk = 0.00000001;
256        risk
257    }
258
259    /// `ConsensusGraphInner` invokes this function to recompute confirmation
260    /// risk of all epochs periodically
261    pub fn update_confirmation_risks(&self, g_inner: &ConsensusGraphInner) {
262        if g_inner.pivot_chain.len() > DEFERRED_STATE_EPOCH_COUNT as usize {
263            let w_0 = g_inner
264                .weight_tree
265                .get(g_inner.cur_era_genesis_block_arena_index);
266            let mut risks = VecDeque::new();
267            let mut epoch_num = g_inner
268                .pivot_index_to_height(g_inner.pivot_chain.len())
269                - DEFERRED_STATE_EPOCH_COUNT;
270            let mut count = 0;
271            while epoch_num > g_inner.cur_era_genesis_height
272                && count < CONFIRMATION_METER_MAX_NUM_MAINTAINED_RISK
273            {
274                let w_4 = self.inner.read().total_weight_in_past_2d.delta;
275                let risk = self.confirmation_risk(g_inner, w_0, w_4, epoch_num);
276                risks.push_front(risk);
277                epoch_num -= 1;
278                count += 1;
279                if risk <= CONFIRMATION_METER_MIN_MAINTAINED_RISK {
280                    break;
281                }
282            }
283
284            if risks.is_empty() {
285                epoch_num = g_inner.cur_era_genesis_height;
286            } else {
287                epoch_num += 1;
288            }
289
290            let finality = &mut self.inner.write().finality_manager;
291            debug!("Confirmation Risk: {:?}", risks);
292            finality.lowest_epoch_num = epoch_num;
293            finality.risks_less_than = risks;
294        }
295    }
296
297    /// This is an expensive function to check whether the current tree graph
298    /// will generate adaptive block under `me` in future. This function is
299    /// used by Conflux to determine when we will remove old snapshots. If
300    /// this is true, we will avoid remove snapshots from the storage layer.
301    pub fn is_adaptive_possible(
302        &self, g_inner: &ConsensusGraphInner, me: usize,
303    ) -> bool {
304        let psi = CONFIRMATION_METER_PSI;
305        // Find the first pivot chain block whose timer diff is less than 140
306        let mut cur_height = g_inner.cur_era_stable_height;
307        let mut cur_arena_index =
308            g_inner.get_pivot_block_arena_index(cur_height);
309        while g_inner.arena[cur_arena_index]
310            .data
311            .ledger_view_timer_chain_height
312            + CONFIRMATION_METER_ADAPTIVE_TEST_TIMER_DIFF
313            <= g_inner.arena[me].data.ledger_view_timer_chain_height
314            && cur_height < g_inner.best_epoch_number()
315        {
316            cur_height += 1;
317            cur_arena_index = g_inner.get_pivot_block_arena_index(cur_height);
318        }
319
320        if cur_height == g_inner.cur_era_stable_height {
321            return false;
322        }
323
324        let mut end_checking_height =
325            (cur_height - g_inner.cur_era_stable_height + psi - 1) / psi * psi
326                + g_inner.cur_era_stable_height;
327        // corner case, should be extremely rare
328        if end_checking_height > g_inner.best_epoch_number() {
329            end_checking_height -= psi;
330        }
331        let n = (end_checking_height - g_inner.cur_era_stable_height) / psi;
332        let total_weight = g_inner
333            .weight_tree
334            .get(g_inner.cur_era_genesis_block_arena_index);
335        let me_index =
336            g_inner.height_to_pivot_index(g_inner.arena[me].data.epoch_number);
337        let x_3 =
338            total_weight - g_inner.pivot_chain_metadata[me_index].past_weight;
339
340        let mut adaptive_risk = 0f64;
341        let d = i128::try_from(g_inner.current_difficulty.low_u128()).unwrap();
342        for i in 0..n {
343            let a_pivot_index = g_inner.height_to_pivot_index(
344                g_inner.cur_era_stable_height + i * psi as u64,
345            );
346            let b_pivot_index = g_inner.height_to_pivot_index(
347                g_inner.cur_era_stable_height + (i + 1) * psi as u64,
348            );
349            let b = g_inner.pivot_chain[b_pivot_index];
350            let y = g_inner.weight_tree.get(b);
351            let mut x_1 = 0;
352            for v in a_pivot_index..b_pivot_index {
353                let pivot = g_inner.pivot_chain[v];
354                let next_pivot = g_inner.pivot_chain[v + 1];
355                for child in &g_inner.arena[pivot].children {
356                    if *child != next_pivot {
357                        let child_subtree_weight =
358                            g_inner.weight_tree.get(*child);
359                        x_1 = max(x_1, child_subtree_weight);
360                    }
361                }
362            }
363            let n_j = (y
364                - x_1
365                - x_3
366                - self.inner.read().total_weight_in_past_2d.delta)
367                / d;
368            let m_j = (total_weight
369                - g_inner.pivot_chain_metadata[a_pivot_index].past_weight)
370                / d;
371
372            let i_risk =
373                10f64.powf((m_j as f64 / 3.0 - n_j as f64) / 700.0 + 5.3);
374            adaptive_risk += i_risk;
375        }
376
377        adaptive_risk > CONFIRMATION_METER_MAXIMUM_ADAPTIVE_RISK
378    }
379}