reth_static_file_types/
lib.rs

1//! Commonly used types for static file usage.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10#![cfg_attr(not(feature = "std"), no_std)]
11
12extern crate alloc;
13
14mod compression;
15mod event;
16mod segment;
17
18use alloy_primitives::BlockNumber;
19pub use compression::Compression;
20use core::ops::RangeInclusive;
21pub use event::StaticFileProducerEvent;
22pub use segment::{SegmentConfig, SegmentHeader, SegmentRangeInclusive, StaticFileSegment};
23
24/// Default static file block count.
25pub const DEFAULT_BLOCKS_PER_STATIC_FILE: u64 = 500_000;
26
27/// Highest static file block numbers, per data segment.
28#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
29pub struct HighestStaticFiles {
30    /// Highest static file block of headers, inclusive.
31    /// If [`None`], no static file is available.
32    pub headers: Option<BlockNumber>,
33    /// Highest static file block of receipts, inclusive.
34    /// If [`None`], no static file is available.
35    pub receipts: Option<BlockNumber>,
36    /// Highest static file block of transactions, inclusive.
37    /// If [`None`], no static file is available.
38    pub transactions: Option<BlockNumber>,
39}
40
41impl HighestStaticFiles {
42    /// Returns the highest static file if it exists for a segment
43    pub const fn highest(&self, segment: StaticFileSegment) -> Option<BlockNumber> {
44        match segment {
45            StaticFileSegment::Headers => self.headers,
46            StaticFileSegment::Transactions => self.transactions,
47            StaticFileSegment::Receipts => self.receipts,
48        }
49    }
50
51    /// Returns a mutable reference to a static file segment
52    pub const fn as_mut(&mut self, segment: StaticFileSegment) -> &mut Option<BlockNumber> {
53        match segment {
54            StaticFileSegment::Headers => &mut self.headers,
55            StaticFileSegment::Transactions => &mut self.transactions,
56            StaticFileSegment::Receipts => &mut self.receipts,
57        }
58    }
59
60    /// Returns an iterator over all static file segments
61    fn iter(&self) -> impl Iterator<Item = Option<BlockNumber>> {
62        [self.headers, self.transactions, self.receipts].into_iter()
63    }
64
65    /// Returns the minimum block of all segments.
66    pub fn min_block_num(&self) -> Option<u64> {
67        self.iter().flatten().min()
68    }
69
70    /// Returns the maximum block of all segments.
71    pub fn max_block_num(&self) -> Option<u64> {
72        self.iter().flatten().max()
73    }
74}
75
76/// Static File targets, per data segment, measured in [`BlockNumber`].
77#[derive(Debug, Clone, Eq, PartialEq)]
78pub struct StaticFileTargets {
79    /// Targeted range of headers.
80    pub headers: Option<RangeInclusive<BlockNumber>>,
81    /// Targeted range of receipts.
82    pub receipts: Option<RangeInclusive<BlockNumber>>,
83    /// Targeted range of transactions.
84    pub transactions: Option<RangeInclusive<BlockNumber>>,
85}
86
87impl StaticFileTargets {
88    /// Returns `true` if any of the targets are [Some].
89    pub const fn any(&self) -> bool {
90        self.headers.is_some() || self.receipts.is_some() || self.transactions.is_some()
91    }
92
93    /// Returns `true` if all targets are either [`None`] or has beginning of the range equal to the
94    /// highest static file.
95    pub fn is_contiguous_to_highest_static_files(&self, static_files: HighestStaticFiles) -> bool {
96        [
97            (self.headers.as_ref(), static_files.headers),
98            (self.receipts.as_ref(), static_files.receipts),
99            (self.transactions.as_ref(), static_files.transactions),
100        ]
101        .iter()
102        .all(|(target_block_range, highest_static_file_block)| {
103            target_block_range.is_none_or(|target_block_range| {
104                *target_block_range.start() ==
105                    highest_static_file_block
106                        .map_or(0, |highest_static_file_block| highest_static_file_block + 1)
107            })
108        })
109    }
110}
111
112/// Each static file has a fixed number of blocks. This gives out the range where the requested
113/// block is positioned. Used for segment filename.
114pub const fn find_fixed_range(
115    block: BlockNumber,
116    blocks_per_static_file: u64,
117) -> SegmentRangeInclusive {
118    let start = (block / blocks_per_static_file) * blocks_per_static_file;
119    SegmentRangeInclusive::new(start, start + blocks_per_static_file - 1)
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_highest_static_files_highest() {
128        let files =
129            HighestStaticFiles { headers: Some(100), receipts: Some(200), transactions: None };
130
131        // Test for headers segment
132        assert_eq!(files.highest(StaticFileSegment::Headers), Some(100));
133
134        // Test for receipts segment
135        assert_eq!(files.highest(StaticFileSegment::Receipts), Some(200));
136
137        // Test for transactions segment
138        assert_eq!(files.highest(StaticFileSegment::Transactions), None);
139    }
140
141    #[test]
142    fn test_highest_static_files_as_mut() {
143        let mut files = HighestStaticFiles::default();
144
145        // Modify headers value
146        *files.as_mut(StaticFileSegment::Headers) = Some(150);
147        assert_eq!(files.headers, Some(150));
148
149        // Modify receipts value
150        *files.as_mut(StaticFileSegment::Receipts) = Some(250);
151        assert_eq!(files.receipts, Some(250));
152
153        // Modify transactions value
154        *files.as_mut(StaticFileSegment::Transactions) = Some(350);
155        assert_eq!(files.transactions, Some(350));
156    }
157
158    #[test]
159    fn test_highest_static_files_min() {
160        let files =
161            HighestStaticFiles { headers: Some(300), receipts: Some(100), transactions: None };
162
163        // Minimum value among the available segments
164        assert_eq!(files.min_block_num(), Some(100));
165
166        let empty_files = HighestStaticFiles::default();
167        // No values, should return None
168        assert_eq!(empty_files.min_block_num(), None);
169    }
170
171    #[test]
172    fn test_highest_static_files_max() {
173        let files =
174            HighestStaticFiles { headers: Some(300), receipts: Some(100), transactions: Some(500) };
175
176        // Maximum value among the available segments
177        assert_eq!(files.max_block_num(), Some(500));
178
179        let empty_files = HighestStaticFiles::default();
180        // No values, should return None
181        assert_eq!(empty_files.max_block_num(), None);
182    }
183
184    #[test]
185    fn test_find_fixed_range() {
186        // Test with default block size
187        let block: BlockNumber = 600_000;
188        let range = find_fixed_range(block, DEFAULT_BLOCKS_PER_STATIC_FILE);
189        assert_eq!(range.start(), 500_000);
190        assert_eq!(range.end(), 999_999);
191
192        // Test with a custom block size
193        let block: BlockNumber = 1_200_000;
194        let range = find_fixed_range(block, 1_000_000);
195        assert_eq!(range.start(), 1_000_000);
196        assert_eq!(range.end(), 1_999_999);
197    }
198}