1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 2020 Conflux Foundation. All rights reserved.
// Conflux is free software and distributed under GNU General Public License.
// See http://www.gnu.org/licenses/

#[derive(Derivative, Clone)]
#[derivative(Debug)]
#[derive(MallocSizeOf)]
pub struct StateAvailabilityBoundary {
    /// This is the hash of blocks in pivot chain based on current graph.
    #[derivative(Debug = "ignore")]
    pub pivot_chain: Vec<H256>,

    pub synced_state_height: u64,
    /// All states of `full_state_space` are available for reading after this
    /// height. `None` means no full state are kept.
    pub full_state_start_height: Option<u64>,
    /// `None` means both spaces are kept.
    /// This field is not used if `full_state_start_height` is `None`.
    pub full_state_space: Option<Space>,

    /// This is the lower boundary height of available state where we can
    /// execute new epochs based on it. Note that `synced_state_height` is
    /// within this bound for execution, but its state cannot be accessed
    /// through `get_state_no_commit`.
    pub lower_bound: u64,
    /// This is the upper boundary height of available state.
    pub upper_bound: u64,
    // Optimistic execution is the feature to execute ahead of the deferred
    // execution boundary. The goal is to pipeline the transaction
    // execution and the block packaging and verification.
    // optimistic_executed_height is the number of step to go ahead
    pub optimistic_executed_height: Option<u64>,
}

impl StateAvailabilityBoundary {
    pub fn new(
        epoch_hash: H256, epoch_height: u64,
        full_state_start_height: Option<u64>, full_state_space: Option<Space>,
    ) -> Self {
        Self {
            pivot_chain: vec![epoch_hash],
            synced_state_height: 0,
            full_state_start_height,
            full_state_space,
            lower_bound: epoch_height,
            upper_bound: epoch_height,
            optimistic_executed_height: None,
        }
    }

    /// Check if the state can be accessed for reading.
    pub fn check_availability(&self, height: u64, block_hash: &H256) -> bool {
        (height == 0 || height != self.synced_state_height)
            && self.lower_bound <= height
            && height <= self.upper_bound
            && {
                let r = self.pivot_chain[(height - self.lower_bound) as usize]
                    == *block_hash;
                if !r {
                    debug!(
                        "pivot_chain={:?} should be {:?} asked is {:?}",
                        self.pivot_chain,
                        self.pivot_chain[(height - self.lower_bound) as usize],
                        block_hash
                    );
                }
                r
            }
    }

    pub fn check_read_availability(
        &self, height: u64, block_hash: &H256, space: Option<Space>,
    ) -> bool {
        self.check_availability(height, block_hash)
            || (height < self.lower_bound
                && self.full_state_available(height, space))
    }

    fn full_state_available(&self, height: u64, space: Option<Space>) -> bool {
        match self.full_state_start_height {
            // No full state
            None => false,
            // Support full state, so check the height and space.
            Some(start_height) => {
                height >= start_height && self.contains_space(&space)
            }
        }
    }

    pub fn contains_space(&self, space: &Option<Space>) -> bool {
        match (space, &self.full_state_space) {
            (_, None) => {
                // We keep the state in all spaces.
                true
            }
            (None, Some(_)) => {
                // We keep a part of states but all states are needed.
                false
            }
            (Some(need_space), Some(kept_space)) => need_space == kept_space,
        }
    }

    /// Try to update `upper_bound` according to a new executed block.
    pub fn adjust_upper_bound(&mut self, executed_block: &BlockHeader) {
        let next_index = (self.upper_bound - self.lower_bound + 1) as usize;
        if next_index < self.pivot_chain.len()
            && executed_block.height() == self.upper_bound + 1
            && executed_block.hash() == self.pivot_chain[next_index]
        {
            self.upper_bound += 1;
        }
    }

    /// This function will record the most recent synced_state_height for
    /// special case handling.
    pub fn set_synced_state_height(&mut self, synced_state_height: u64) {
        self.synced_state_height = synced_state_height;
    }

    /// This function will set a new lower boundary height of available state.
    /// Caller should make sure the new lower boundary height should be greater
    /// than or equal to current lower boundary height.
    /// Caller should also make sure the new lower boundary height should be
    /// less than or equal to current upper boundary height.
    pub fn adjust_lower_bound(&mut self, new_lower_bound: u64) {
        // If we are going to call this function, `upper_bound` will not be 0
        // unless it is a full node and is in header phase. And we should do
        // nothing in this case.
        if self.upper_bound == 0 {
            return;
        }
        assert!(self.lower_bound <= new_lower_bound);
        assert!(
            new_lower_bound <= self.upper_bound,
            "however {} > {}, self {:?}",
            new_lower_bound,
            self.upper_bound,
            self,
        );
        if self.synced_state_height != 0
            && new_lower_bound > self.synced_state_height + REWARD_EPOCH_COUNT
        {
            self.synced_state_height = 0;
        }
        self.pivot_chain = self
            .pivot_chain
            .split_off((new_lower_bound - self.lower_bound) as usize);
        self.lower_bound = new_lower_bound;
    }
}

use cfx_parameters::consensus_internal::REWARD_EPOCH_COUNT;
use cfx_types::{Space, H256};
use derivative::Derivative;
use malloc_size_of_derive::MallocSizeOf;
use primitives::BlockHeader;