Skip to main content

reth_provider/providers/static_file/
jar.rs

1use super::{
2    metrics::{StaticFileProviderMetrics, StaticFileProviderOperation},
3    LoadedJarRef,
4};
5use crate::{
6    to_range, BlockHashReader, BlockNumReader, HeaderProvider, ReceiptProvider,
7    TransactionsProvider,
8};
9use alloy_consensus::transaction::TransactionMeta;
10use alloy_eips::{eip2718::Encodable2718, BlockHashOrNumber};
11use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash, TxNumber, B256};
12use reth_chainspec::ChainInfo;
13use reth_db::static_file::{
14    BlockHashMask, HeaderMask, HeaderWithHashMask, ReceiptMask, StaticFileCursor, TransactionMask,
15    TransactionSenderMask,
16};
17use reth_db_api::table::{Decompress, Value};
18use reth_node_types::NodePrimitives;
19use reth_primitives_traits::{SealedHeader, SignedTransaction};
20use reth_static_file_types::ChangesetOffset;
21use reth_storage_api::range_size_hint;
22use reth_storage_errors::provider::{ProviderError, ProviderResult};
23use std::{
24    fmt::Debug,
25    ops::{Deref, RangeBounds, RangeInclusive},
26    sync::Arc,
27};
28/// Provider over a specific `NippyJar` and range.
29#[derive(Debug)]
30pub struct StaticFileJarProvider<'a, N> {
31    /// Main static file segment
32    jar: LoadedJarRef<'a>,
33    /// Another kind of static file segment to help query data from the main one.
34    auxiliary_jar: Option<Box<Self>>,
35    /// Metrics for the static files.
36    metrics: Option<Arc<StaticFileProviderMetrics>>,
37    /// Node primitives
38    _pd: std::marker::PhantomData<N>,
39}
40
41impl<'a, N: NodePrimitives> Deref for StaticFileJarProvider<'a, N> {
42    type Target = LoadedJarRef<'a>;
43    fn deref(&self) -> &Self::Target {
44        &self.jar
45    }
46}
47
48impl<'a, N: NodePrimitives> From<LoadedJarRef<'a>> for StaticFileJarProvider<'a, N> {
49    fn from(value: LoadedJarRef<'a>) -> Self {
50        StaticFileJarProvider {
51            jar: value,
52            auxiliary_jar: None,
53            metrics: None,
54            _pd: Default::default(),
55        }
56    }
57}
58
59impl<'a, N: NodePrimitives> StaticFileJarProvider<'a, N> {
60    /// Provides a cursor for more granular data access.
61    pub fn cursor<'b>(&'b self) -> ProviderResult<StaticFileCursor<'a>>
62    where
63        'b: 'a,
64    {
65        let result = StaticFileCursor::new(self.value(), self.mmap_handle())?;
66
67        if let Some(metrics) = &self.metrics {
68            metrics.record_segment_operation(
69                self.segment(),
70                StaticFileProviderOperation::InitCursor,
71                None,
72            );
73        }
74
75        Ok(result)
76    }
77
78    /// Adds a new auxiliary static file to help query data from the main one
79    pub fn with_auxiliary(mut self, auxiliary_jar: Self) -> Self {
80        self.auxiliary_jar = Some(Box::new(auxiliary_jar));
81        self
82    }
83
84    /// Enables metrics on the provider.
85    pub fn with_metrics(mut self, metrics: Arc<StaticFileProviderMetrics>) -> Self {
86        self.metrics = Some(metrics);
87        self
88    }
89
90    /// Returns the total size of the data and offsets files (from the in-memory mmap).
91    pub fn size(&self) -> usize {
92        self.jar.value().size()
93    }
94
95    /// Reads a changeset offset from the sidecar file for a given block.
96    ///
97    /// Returns `None` if:
98    /// - The segment is not change-based
99    /// - The block is not in the block range
100    /// - The sidecar file doesn't exist
101    pub fn read_changeset_offset(
102        &self,
103        block_number: BlockNumber,
104    ) -> ProviderResult<Option<ChangesetOffset>> {
105        let header = self.user_header();
106        if !header.segment().is_change_based() {
107            return Ok(None);
108        }
109
110        let Some(index) = header.changeset_offset_index(block_number) else {
111            return Ok(None);
112        };
113
114        if let Some(reader) = self.jar.value().csoff_reader() {
115            reader.get(index).map_err(ProviderError::other)
116        } else {
117            Ok(None)
118        }
119    }
120
121    /// Reads all changeset offsets from the sidecar file.
122    ///
123    /// Returns `None` if:
124    /// - The segment is not change-based
125    /// - The sidecar file doesn't exist
126    pub fn read_changeset_offsets(&self) -> ProviderResult<Option<Vec<ChangesetOffset>>> {
127        let header = self.user_header();
128        if !header.segment().is_change_based() {
129            return Ok(None);
130        }
131
132        let len = header.changeset_offsets_len();
133        if len == 0 {
134            return Ok(Some(Vec::new()));
135        }
136
137        if let Some(reader) = self.jar.value().csoff_reader() {
138            let offsets = reader.get_range(0, len).map_err(ProviderError::other)?;
139            Ok(Some(offsets))
140        } else {
141            Ok(None)
142        }
143    }
144}
145
146impl<N: NodePrimitives<BlockHeader: Value>> HeaderProvider for StaticFileJarProvider<'_, N> {
147    type Header = N::BlockHeader;
148
149    fn header(&self, block_hash: BlockHash) -> ProviderResult<Option<Self::Header>> {
150        Ok(self
151            .cursor()?
152            .get_two::<HeaderWithHashMask<Self::Header>>((&block_hash).into())?
153            .filter(|(_, hash)| hash == &block_hash)
154            .map(|(header, _)| header))
155    }
156
157    fn header_by_number(&self, num: BlockNumber) -> ProviderResult<Option<Self::Header>> {
158        self.cursor()?.get_one::<HeaderMask<Self::Header>>(num.into())
159    }
160
161    fn headers_range(
162        &self,
163        range: impl RangeBounds<BlockNumber>,
164    ) -> ProviderResult<Vec<Self::Header>> {
165        let mut cursor = self.cursor()?;
166        let mut headers = Vec::with_capacity(range_size_hint(&range).unwrap_or(1024));
167
168        for num in to_range(range) {
169            if let Some(header) = cursor.get_one::<HeaderMask<Self::Header>>(num.into())? {
170                headers.push(header);
171            }
172        }
173
174        Ok(headers)
175    }
176
177    fn sealed_header(
178        &self,
179        number: BlockNumber,
180    ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
181        Ok(self
182            .cursor()?
183            .get_two::<HeaderWithHashMask<Self::Header>>(number.into())?
184            .map(|(header, hash)| SealedHeader::new(header, hash)))
185    }
186
187    fn sealed_headers_while(
188        &self,
189        range: impl RangeBounds<BlockNumber>,
190        mut predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
191    ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
192        let mut cursor = self.cursor()?;
193        let mut headers = Vec::with_capacity(range_size_hint(&range).unwrap_or(1024));
194
195        for number in to_range(range) {
196            if let Some((header, hash)) =
197                cursor.get_two::<HeaderWithHashMask<Self::Header>>(number.into())?
198            {
199                let sealed = SealedHeader::new(header, hash);
200                if !predicate(&sealed) {
201                    break
202                }
203                headers.push(sealed);
204            }
205        }
206        Ok(headers)
207    }
208}
209
210impl<N: NodePrimitives> BlockHashReader for StaticFileJarProvider<'_, N> {
211    fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
212        self.cursor()?.get_one::<BlockHashMask>(number.into())
213    }
214
215    fn canonical_hashes_range(
216        &self,
217        start: BlockNumber,
218        end: BlockNumber,
219    ) -> ProviderResult<Vec<B256>> {
220        let mut cursor = self.cursor()?;
221        let mut hashes = Vec::with_capacity((end - start) as usize);
222
223        for number in start..end {
224            if let Some(hash) = cursor.get_one::<BlockHashMask>(number.into())? {
225                hashes.push(hash)
226            }
227        }
228        Ok(hashes)
229    }
230}
231
232impl<N: NodePrimitives> BlockNumReader for StaticFileJarProvider<'_, N> {
233    fn chain_info(&self) -> ProviderResult<ChainInfo> {
234        // Information on live database
235        Err(ProviderError::UnsupportedProvider)
236    }
237
238    fn best_block_number(&self) -> ProviderResult<BlockNumber> {
239        // Information on live database
240        Err(ProviderError::UnsupportedProvider)
241    }
242
243    fn last_block_number(&self) -> ProviderResult<BlockNumber> {
244        // Information on live database
245        Err(ProviderError::UnsupportedProvider)
246    }
247
248    fn block_number(&self, hash: B256) -> ProviderResult<Option<BlockNumber>> {
249        let mut cursor = self.cursor()?;
250
251        Ok(cursor
252            .get_one::<BlockHashMask>((&hash).into())?
253            .and_then(|res| (res == hash).then(|| cursor.number()).flatten()))
254    }
255}
256
257impl<N: NodePrimitives<SignedTx: Decompress + SignedTransaction>> TransactionsProvider
258    for StaticFileJarProvider<'_, N>
259{
260    type Transaction = N::SignedTx;
261
262    fn transaction_id(&self, hash: TxHash) -> ProviderResult<Option<TxNumber>> {
263        let mut cursor = self.cursor()?;
264
265        Ok(cursor
266            .get_one::<TransactionMask<Self::Transaction>>((&hash).into())?
267            .and_then(|res| (res.trie_hash() == hash).then(|| cursor.number()).flatten()))
268    }
269
270    fn transaction_by_id(&self, num: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
271        self.cursor()?.get_one::<TransactionMask<Self::Transaction>>(num.into())
272    }
273
274    fn transaction_by_id_unhashed(
275        &self,
276        num: TxNumber,
277    ) -> ProviderResult<Option<Self::Transaction>> {
278        self.cursor()?.get_one::<TransactionMask<Self::Transaction>>(num.into())
279    }
280
281    fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
282        self.cursor()?.get_one::<TransactionMask<Self::Transaction>>((&hash).into())
283    }
284
285    fn transaction_by_hash_with_meta(
286        &self,
287        _hash: TxHash,
288    ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
289        // Information required on indexing table [`tables::TransactionBlocks`]
290        Err(ProviderError::UnsupportedProvider)
291    }
292
293    fn transactions_by_block(
294        &self,
295        _block_id: BlockHashOrNumber,
296    ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
297        // Related to indexing tables. Live database should get the tx_range and call static file
298        // provider with `transactions_by_tx_range` instead.
299        Err(ProviderError::UnsupportedProvider)
300    }
301
302    fn transactions_by_block_range(
303        &self,
304        _range: impl RangeBounds<BlockNumber>,
305    ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
306        // Related to indexing tables. Live database should get the tx_range and call static file
307        // provider with `transactions_by_tx_range` instead.
308        Err(ProviderError::UnsupportedProvider)
309    }
310
311    fn transactions_by_tx_range(
312        &self,
313        range: impl RangeBounds<TxNumber>,
314    ) -> ProviderResult<Vec<Self::Transaction>> {
315        let mut cursor = self.cursor()?;
316        let mut txs = Vec::with_capacity(range_size_hint(&range).unwrap_or(1024));
317
318        for num in to_range(range) {
319            if let Some(tx) = cursor.get_one::<TransactionMask<Self::Transaction>>(num.into())? {
320                txs.push(tx)
321            }
322        }
323        Ok(txs)
324    }
325
326    fn senders_by_tx_range(
327        &self,
328        range: impl RangeBounds<TxNumber>,
329    ) -> ProviderResult<Vec<Address>> {
330        let mut cursor = self.cursor()?;
331        let mut senders = Vec::with_capacity(range_size_hint(&range).unwrap_or(1024));
332
333        for num in to_range(range) {
334            if let Some(tx) = cursor.get_one::<TransactionSenderMask>(num.into())? {
335                senders.push(tx)
336            }
337        }
338        Ok(senders)
339    }
340
341    fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
342        self.cursor()?.get_one::<TransactionSenderMask>(id.into())
343    }
344}
345
346impl<N: NodePrimitives<SignedTx: Decompress + SignedTransaction, Receipt: Decompress>>
347    ReceiptProvider for StaticFileJarProvider<'_, N>
348{
349    type Receipt = N::Receipt;
350
351    fn receipt(&self, num: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
352        self.cursor()?.get_one::<ReceiptMask<Self::Receipt>>(num.into())
353    }
354
355    fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
356        if let Some(tx_static_file) = &self.auxiliary_jar &&
357            let Some(num) = tx_static_file.transaction_id(hash)?
358        {
359            return self.receipt(num)
360        }
361        Ok(None)
362    }
363
364    fn receipts_by_block(
365        &self,
366        _block: BlockHashOrNumber,
367    ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
368        // Related to indexing tables. StaticFile should get the tx_range and call static file
369        // provider with `receipt()` instead for each
370        Err(ProviderError::UnsupportedProvider)
371    }
372
373    fn receipts_by_tx_range(
374        &self,
375        range: impl RangeBounds<TxNumber>,
376    ) -> ProviderResult<Vec<Self::Receipt>> {
377        let mut cursor = self.cursor()?;
378        let mut receipts = Vec::with_capacity(range_size_hint(&range).unwrap_or(1024));
379
380        for num in to_range(range) {
381            if let Some(tx) = cursor.get_one::<ReceiptMask<Self::Receipt>>(num.into())? {
382                receipts.push(tx)
383            }
384        }
385        Ok(receipts)
386    }
387
388    fn receipts_by_block_range(
389        &self,
390        _block_range: RangeInclusive<BlockNumber>,
391    ) -> ProviderResult<Vec<Vec<Self::Receipt>>> {
392        // Related to indexing tables. StaticFile should get the tx_range and call static file
393        // provider with `receipt()` instead for each
394        Err(ProviderError::UnsupportedProvider)
395    }
396}