Skip to main content

ethrex_storage/
block_data_buffer.rs

1use std::collections::VecDeque;
2use std::sync::Arc;
3
4use ethrex_common::H256;
5use ethrex_common::types::{Block, BlockBody, BlockHash, BlockHeader, BlockNumber, Code, Receipt};
6use ethrex_crypto::NativeCrypto;
7use rustc_hash::{FxHashMap, FxHashSet};
8
9/// Block data held in memory after `newPayload` returns, before the background
10/// flusher writes it to RocksDB. One per block hash (holds reorg siblings).
11#[derive(Debug, Clone)]
12pub struct BufferedBlock {
13    pub header: BlockHeader,
14    pub body: BlockBody,
15    pub number: BlockNumber,
16    pub receipts: Vec<Receipt>,
17}
18
19/// In-memory overlay for not-yet-flushed block data. Consulted before disk by
20/// every read of headers/bodies/numbers/receipts/codes/tx-locations.
21///
22/// Held behind `Arc<RwLock<Arc<BlockDataBuffer>>>` in `Store`. Readers clone
23/// the inner `Arc` under a brief read lock and then work lock-free; the single
24/// writer mutates a clone and RCU-swaps the `Arc` in. Both critical sections
25/// are O(1) pointer operations.
26#[derive(Debug, Clone)]
27pub struct BlockDataBuffer {
28    by_hash: FxHashMap<BlockHash, Arc<BufferedBlock>>,
29    by_number: FxHashMap<BlockNumber, Vec<BlockHash>>,
30    tx_index: FxHashMap<H256, Vec<(BlockNumber, BlockHash, u64)>>,
31    /// Content-addressed code introduced by buffered blocks, with the block
32    /// number that introduced it (for eviction).
33    codes: FxHashMap<H256, (Code, BlockNumber)>,
34    /// Hashes not yet known to be on disk, oldest first.
35    unflushed: VecDeque<BlockHash>,
36    /// Highest block number whose data is durably on disk.
37    flushed_upto: BlockNumber,
38}
39
40impl Default for BlockDataBuffer {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl BlockDataBuffer {
47    pub fn new() -> Self {
48        Self {
49            by_hash: FxHashMap::default(),
50            by_number: FxHashMap::default(),
51            tx_index: FxHashMap::default(),
52            codes: FxHashMap::default(),
53            unflushed: VecDeque::new(),
54            flushed_upto: 0,
55        }
56    }
57
58    /// Takes ownership of `block` and moves its header/body into the buffer (the
59    /// caller hands ownership over the flush channel and drops it right after, so
60    /// cloning would be pure waste).
61    pub fn insert(&mut self, block: Block, receipts: Vec<Receipt>, codes: Vec<(H256, Code)>) {
62        let hash = block.hash();
63        let number = block.header.number;
64        if self.by_hash.contains_key(&hash) {
65            return; // duplicate guard
66        }
67        for (index, tx) in block.body.transactions.iter().enumerate() {
68            self.tx_index
69                .entry(tx.hash(&NativeCrypto))
70                .or_default()
71                .push((number, hash, index as u64));
72        }
73        for (code_hash, code) in codes {
74            self.codes.entry(code_hash).or_insert((code, number));
75        }
76        self.by_number.entry(number).or_default().push(hash);
77        let Block { header, body } = block;
78        self.by_hash.insert(
79            hash,
80            Arc::new(BufferedBlock {
81                header,
82                body,
83                number,
84                receipts,
85            }),
86        );
87        self.unflushed.push_back(hash);
88    }
89
90    pub fn get_header(&self, hash: &BlockHash) -> Option<BlockHeader> {
91        self.by_hash.get(hash).map(|b| b.header.clone())
92    }
93
94    pub fn get_body(&self, hash: &BlockHash) -> Option<BlockBody> {
95        self.by_hash.get(hash).map(|b| b.body.clone())
96    }
97
98    pub fn get_number(&self, hash: &BlockHash) -> Option<BlockNumber> {
99        self.by_hash.get(hash).map(|b| b.number)
100    }
101
102    pub fn get_receipt(&self, hash: &BlockHash, index: u64) -> Option<Receipt> {
103        self.by_hash
104            .get(hash)
105            .and_then(|b| b.receipts.get(index as usize).cloned())
106    }
107
108    pub fn get_receipts(&self, hash: &BlockHash) -> Option<Vec<Receipt>> {
109        self.by_hash.get(hash).map(|b| b.receipts.clone())
110    }
111
112    pub fn get_code(&self, code_hash: &H256) -> Option<Code> {
113        self.codes.get(code_hash).map(|(c, _)| c.clone())
114    }
115
116    pub fn get_tx_locations(&self, tx_hash: &H256) -> Vec<(BlockNumber, BlockHash, u64)> {
117        self.tx_index.get(tx_hash).cloned().unwrap_or_default()
118    }
119
120    pub fn flushed_upto(&self) -> BlockNumber {
121        self.flushed_upto
122    }
123
124    pub fn set_flushed_upto(&mut self, n: BlockNumber) {
125        self.flushed_upto = self.flushed_upto.max(n);
126    }
127
128    /// All unflushed blocks, ascending by number (deterministic flush order).
129    pub fn flushable(&self) -> Vec<Arc<BufferedBlock>> {
130        let mut out: Vec<Arc<BufferedBlock>> = self
131            .unflushed
132            .iter()
133            .filter_map(|h| self.by_hash.get(h).cloned())
134            .collect();
135        out.sort_by_key(|b| b.number);
136        out
137    }
138
139    /// Codes introduced by the given block hashes (for the flush write tx).
140    pub fn codes_for(&self, hashes: &[BlockHash]) -> Vec<(H256, Code)> {
141        let numbers: FxHashSet<BlockNumber> =
142            hashes.iter().filter_map(|h| self.get_number(h)).collect();
143        self.codes
144            .iter()
145            .filter(|(_, (_, n))| numbers.contains(n))
146            .map(|(h, (c, _))| (*h, c.clone()))
147            .collect()
148    }
149
150    /// Drop all buffered data for blocks with number <= `new_flushed_upto` and
151    /// advance the durable marker. Called only after the disk write committed.
152    pub fn evict_flushed(&mut self, new_flushed_upto: BlockNumber) {
153        self.set_flushed_upto(new_flushed_upto);
154        let drop_hashes: Vec<BlockHash> = self
155            .by_hash
156            .iter()
157            .filter(|(_, b)| b.number <= new_flushed_upto)
158            .map(|(h, _)| *h)
159            .collect();
160        for h in &drop_hashes {
161            if let Some(b) = self.by_hash.remove(h) {
162                if let Some(v) = self.by_number.get_mut(&b.number) {
163                    v.retain(|x| x != h);
164                    if v.is_empty() {
165                        self.by_number.remove(&b.number);
166                    }
167                }
168                for tx in b.body.transactions.iter() {
169                    if let Some(v) = self.tx_index.get_mut(&tx.hash(&NativeCrypto)) {
170                        v.retain(|(_, bh, _)| bh != h);
171                        if v.is_empty() {
172                            self.tx_index.remove(&tx.hash(&NativeCrypto));
173                        }
174                    }
175                }
176            }
177        }
178        self.unflushed.retain(|h| self.by_hash.contains_key(h));
179        self.codes.retain(|_, (_, n)| *n > new_flushed_upto);
180    }
181}