cfx_rpc/helpers/
fee_history_cache.rs

1#![allow(dead_code)]
2use cfx_rpc_cfx_types::{FeeHistoryCacheEntry, PhantomBlock};
3use cfx_types::{Space, H256};
4use parking_lot::RwLock;
5use std::{collections::VecDeque, sync::Arc};
6
7pub const MAX_FEE_HISTORY_CACHE_BLOCK_COUNT: u64 = 1024;
8
9#[derive(Debug, Clone)]
10pub struct FeeHistoryCache {
11    inner: Arc<RwLock<FeeHistoryCacheInner>>,
12}
13
14impl FeeHistoryCache {
15    pub fn new() -> Self {
16        Self {
17            inner: Arc::new(RwLock::new(FeeHistoryCacheInner::new())),
18        }
19    }
20
21    /*
22        This cache is used to store the FeeHistoryEntry data for the latest 1024 blocks, enabling quick queries.
23
24        Update Logic:
25        If the cached block data is outdated, clear the cache first.
26        If the cache is empty, fetch the FeeHistoryEntry data for the latest q blocks directly from the DB and cache them (q is the number of blocks to be queried this time).
27        If the cache is not empty, get the upper bound of the cache and update from that block number to the latest block.
28        If the number of blocks in the cache exceeds 1024, delete the oldest block data until the number of blocks in the cache is 1024.
29    */
30    pub fn update_to_latest_block<F>(
31        &self, latest_block: u64, latest_hash: H256, query_len: u64,
32        fetch_block_by_hash: F,
33    ) -> Result<(), String>
34    where
35        F: Fn(H256) -> Result<PhantomBlock, String>,
36    {
37        let mut inner = self.inner.write();
38
39        // if the cached block data is outdated, clear the cache first
40        inner.check_and_clear_cache(latest_block);
41
42        let start_block = if inner.is_empty() {
43            if latest_block <= query_len {
44                0
45            } else {
46                latest_block - query_len + 1
47            }
48        } else {
49            inner.upper_bound() + 1
50        };
51
52        let mut curr_hash = latest_hash;
53        let mut container = VecDeque::new();
54        for i in (start_block..=latest_block).rev() {
55            let block = fetch_block_by_hash(curr_hash)?;
56            container.push_front((
57                i,
58                FeeHistoryCacheEntry::from_block(
59                    Space::Ethereum,
60                    &block.pivot_header,
61                    block.transactions.iter().map(|x| &**x),
62                ),
63            ));
64            curr_hash = block.pivot_header.parent_hash().clone();
65        }
66
67        for (block_number, entry) in container {
68            inner.push_back(block_number, entry)?;
69        }
70
71        // update cache if block changes due to reorg
72        if inner.lower_bound < start_block {
73            let need_check = start_block - inner.lower_bound;
74            for item in inner.entries.iter_mut().take(need_check as usize).rev()
75            {
76                if item.header_hash == curr_hash {
77                    break;
78                }
79                let block = fetch_block_by_hash(curr_hash)?;
80                *item = FeeHistoryCacheEntry::from_block(
81                    Space::Ethereum,
82                    &block.pivot_header,
83                    block.transactions.iter().map(|x| &**x),
84                );
85                curr_hash = block.pivot_header.parent_hash().clone();
86            }
87        }
88
89        Ok(())
90    }
91
92    pub fn max_blocks(&self) -> u64 { self.inner.read().max_blocks }
93
94    pub fn lower_bound(&self) -> u64 { self.inner.read().lower_bound }
95
96    pub fn upper_bound(&self) -> u64 { self.inner.read().upper_bound() }
97
98    pub fn get_history(
99        &self, start_block: u64, end_block: u64,
100    ) -> Option<Vec<FeeHistoryCacheEntry>> {
101        let inner = self.inner.read();
102        let lower_bound = inner.lower_bound;
103        let upper_bound = inner.upper_bound();
104        if start_block >= lower_bound && end_block <= upper_bound {
105            let start = (start_block - lower_bound) as usize;
106            let end = (end_block - lower_bound) as usize;
107            let result = inner
108                .entries
109                .range(start..=end)
110                .map(|fee_entry| fee_entry.clone())
111                .collect::<Vec<_>>();
112
113            if result.is_empty() {
114                return None;
115            }
116
117            Some(result)
118        } else {
119            None
120        }
121    }
122
123    pub fn get_history_with_missing_info(
124        &self, start_block: u64, end_block: u64,
125    ) -> Vec<Option<FeeHistoryCacheEntry>> {
126        let inner = self.inner.read();
127        (start_block..=end_block)
128            .map(|block_number| inner.get(block_number))
129            .collect()
130    }
131}
132
133#[derive(Debug)]
134struct FeeHistoryCacheInner {
135    /// Stores the lower bound of the cache
136    lower_bound: u64,
137    /// maximum number of blocks to store in the cache
138    max_blocks: u64,
139    /// Stores the entries of the cache
140    entries: VecDeque<FeeHistoryCacheEntry>,
141}
142
143impl FeeHistoryCacheInner {
144    pub fn new() -> Self {
145        Self {
146            lower_bound: 0,
147            max_blocks: MAX_FEE_HISTORY_CACHE_BLOCK_COUNT,
148            entries: VecDeque::new(),
149        }
150    }
151
152    pub fn upper_bound(&self) -> u64 {
153        self.lower_bound + self.entries.len() as u64 - 1
154    }
155
156    // if the cached history is outdated, clear the cache
157    fn check_and_clear_cache(&mut self, latest_block: u64) {
158        if !self.is_empty()
159            && self.upper_bound() + self.max_blocks <= latest_block
160        {
161            self.clear_cache();
162        }
163    }
164
165    pub fn clear_cache(&mut self) {
166        self.entries.clear();
167        self.lower_bound = 0;
168    }
169
170    fn push_back(
171        &mut self, block_number: u64, entry: FeeHistoryCacheEntry,
172    ) -> Result<(), String> {
173        if !self.is_empty() && block_number - self.upper_bound() != 1 {
174            return Err("block number is not consecutive".to_string());
175        }
176
177        self.entries.push_back(entry);
178
179        // the first entry
180        if self.lower_bound == 0 {
181            self.lower_bound = block_number;
182        }
183
184        if self.entries.len() > self.max_blocks as usize {
185            self.entries.pop_front();
186            self.lower_bound += 1;
187        }
188
189        Ok(())
190    }
191
192    pub fn is_empty(&self) -> bool { self.entries.is_empty() }
193
194    pub fn get(&self, height: u64) -> Option<FeeHistoryCacheEntry> {
195        if height < self.lower_bound || height > self.upper_bound() {
196            return None;
197        }
198        let key = height - self.lower_bound;
199        self.entries.get(key as usize).map(|item| item.clone())
200    }
201
202    #[allow(dead_code)]
203    pub fn update(&mut self, height: u64, entry: FeeHistoryCacheEntry) {
204        if height < self.lower_bound || height > self.upper_bound() {
205            return;
206        }
207        let key = height - self.lower_bound;
208        if let Some(item) = self.entries.get_mut(key as usize) {
209            *item = entry;
210        }
211    }
212}