1use crate::{BlockNumber, Compression};
2use alloy_primitives::TxNumber;
3use derive_more::Display;
4use serde::{Deserialize, Serialize};
5use std::{ops::RangeInclusive, str::FromStr};
6use strum::{AsRefStr, EnumIter, EnumString};
7
8#[derive(
9 Debug,
10 Copy,
11 Clone,
12 Eq,
13 PartialEq,
14 Hash,
15 Ord,
16 PartialOrd,
17 Deserialize,
18 Serialize,
19 EnumString,
20 EnumIter,
21 AsRefStr,
22 Display,
23)]
24#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
25pub enum StaticFileSegment {
27 #[strum(serialize = "headers")]
28 Headers,
31 #[strum(serialize = "transactions")]
32 Transactions,
34 #[strum(serialize = "receipts")]
35 Receipts,
37}
38
39impl StaticFileSegment {
40 pub const fn as_str(&self) -> &'static str {
42 match self {
43 Self::Headers => "headers",
44 Self::Transactions => "transactions",
45 Self::Receipts => "receipts",
46 }
47 }
48
49 pub const fn config(&self) -> SegmentConfig {
51 SegmentConfig { compression: Compression::Lz4 }
52 }
53
54 pub const fn columns(&self) -> usize {
56 match self {
57 Self::Headers => 3,
58 Self::Transactions | Self::Receipts => 1,
59 }
60 }
61
62 pub fn filename(&self, block_range: &SegmentRangeInclusive) -> String {
64 format!("static_file_{}_{}_{}", self.as_ref(), block_range.start(), block_range.end())
67 }
68
69 pub fn filename_with_configuration(
71 &self,
72 compression: Compression,
73 block_range: &SegmentRangeInclusive,
74 ) -> String {
75 let prefix = self.filename(block_range);
76
77 let filters_name = "none".to_string();
78
79 format!("{prefix}_{}_{}", filters_name, compression.as_ref())
82 }
83
84 pub fn parse_filename(name: &str) -> Option<(Self, SegmentRangeInclusive)> {
101 let mut parts = name.split('_');
102 if !(parts.next() == Some("static") && parts.next() == Some("file")) {
103 return None
104 }
105
106 let segment = Self::from_str(parts.next()?).ok()?;
107 let (block_start, block_end) = (parts.next()?.parse().ok()?, parts.next()?.parse().ok()?);
108
109 if block_start > block_end {
110 return None
111 }
112
113 Some((segment, SegmentRangeInclusive::new(block_start, block_end)))
114 }
115
116 pub const fn is_headers(&self) -> bool {
118 matches!(self, Self::Headers)
119 }
120
121 pub const fn is_receipts(&self) -> bool {
123 matches!(self, Self::Receipts)
124 }
125
126 pub const fn is_tx_based(&self) -> bool {
129 matches!(self, Self::Receipts | Self::Transactions)
130 }
131}
132
133#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
135pub struct SegmentHeader {
136 expected_block_range: SegmentRangeInclusive,
141 block_range: Option<SegmentRangeInclusive>,
143 tx_range: Option<SegmentRangeInclusive>,
145 segment: StaticFileSegment,
147}
148
149impl SegmentHeader {
150 pub const fn new(
152 expected_block_range: SegmentRangeInclusive,
153 block_range: Option<SegmentRangeInclusive>,
154 tx_range: Option<SegmentRangeInclusive>,
155 segment: StaticFileSegment,
156 ) -> Self {
157 Self { expected_block_range, block_range, tx_range, segment }
158 }
159
160 pub const fn segment(&self) -> StaticFileSegment {
162 self.segment
163 }
164
165 pub const fn block_range(&self) -> Option<&SegmentRangeInclusive> {
167 self.block_range.as_ref()
168 }
169
170 pub const fn tx_range(&self) -> Option<&SegmentRangeInclusive> {
172 self.tx_range.as_ref()
173 }
174
175 pub const fn expected_block_start(&self) -> BlockNumber {
177 self.expected_block_range.start()
178 }
179
180 pub const fn expected_block_end(&self) -> BlockNumber {
182 self.expected_block_range.end()
183 }
184
185 pub fn block_start(&self) -> Option<BlockNumber> {
187 self.block_range.as_ref().map(|b| b.start())
188 }
189
190 pub fn block_end(&self) -> Option<BlockNumber> {
192 self.block_range.as_ref().map(|b| b.end())
193 }
194
195 pub fn tx_start(&self) -> Option<TxNumber> {
197 self.tx_range.as_ref().map(|t| t.start())
198 }
199
200 pub fn tx_end(&self) -> Option<TxNumber> {
202 self.tx_range.as_ref().map(|t| t.end())
203 }
204
205 pub fn tx_len(&self) -> Option<u64> {
207 self.tx_range.as_ref().map(|r| (r.end() + 1) - r.start())
208 }
209
210 pub fn block_len(&self) -> Option<u64> {
212 self.block_range.as_ref().map(|r| (r.end() + 1) - r.start())
213 }
214
215 pub fn increment_block(&mut self) -> BlockNumber {
217 if let Some(block_range) = &mut self.block_range {
218 block_range.end += 1;
219 block_range.end
220 } else {
221 self.block_range = Some(SegmentRangeInclusive::new(
222 self.expected_block_start(),
223 self.expected_block_start(),
224 ));
225 self.expected_block_start()
226 }
227 }
228
229 pub fn increment_tx(&mut self) {
231 match self.segment {
232 StaticFileSegment::Headers => (),
233 StaticFileSegment::Transactions | StaticFileSegment::Receipts => {
234 if let Some(tx_range) = &mut self.tx_range {
235 tx_range.end += 1;
236 } else {
237 self.tx_range = Some(SegmentRangeInclusive::new(0, 0));
238 }
239 }
240 }
241 }
242
243 pub fn prune(&mut self, num: u64) {
245 match self.segment {
246 StaticFileSegment::Headers => {
247 if let Some(range) = &mut self.block_range {
248 if num > range.end - range.start {
249 self.block_range = None;
250 } else {
251 range.end = range.end.saturating_sub(num);
252 }
253 };
254 }
255 StaticFileSegment::Transactions | StaticFileSegment::Receipts => {
256 if let Some(range) = &mut self.tx_range {
257 if num > range.end - range.start {
258 self.tx_range = None;
259 } else {
260 range.end = range.end.saturating_sub(num);
261 }
262 };
263 }
264 };
265 }
266
267 pub fn set_block_range(&mut self, block_start: BlockNumber, block_end: BlockNumber) {
269 if let Some(block_range) = &mut self.block_range {
270 block_range.start = block_start;
271 block_range.end = block_end;
272 } else {
273 self.block_range = Some(SegmentRangeInclusive::new(block_start, block_end))
274 }
275 }
276
277 pub fn set_tx_range(&mut self, tx_start: TxNumber, tx_end: TxNumber) {
279 if let Some(tx_range) = &mut self.tx_range {
280 tx_range.start = tx_start;
281 tx_range.end = tx_end;
282 } else {
283 self.tx_range = Some(SegmentRangeInclusive::new(tx_start, tx_end))
284 }
285 }
286
287 pub fn start(&self) -> Option<u64> {
289 match self.segment {
290 StaticFileSegment::Headers => self.block_start(),
291 StaticFileSegment::Transactions | StaticFileSegment::Receipts => self.tx_start(),
292 }
293 }
294}
295
296#[derive(Debug, Clone, Copy)]
298pub struct SegmentConfig {
299 pub compression: Compression,
301}
302
303#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
307pub struct SegmentRangeInclusive {
308 start: u64,
309 end: u64,
310}
311
312impl SegmentRangeInclusive {
313 pub const fn new(start: u64, end: u64) -> Self {
315 Self { start, end }
316 }
317
318 pub const fn start(&self) -> u64 {
320 self.start
321 }
322
323 pub const fn end(&self) -> u64 {
325 self.end
326 }
327}
328
329impl std::fmt::Display for SegmentRangeInclusive {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 write!(f, "{}..={}", self.start, self.end)
332 }
333}
334
335impl From<RangeInclusive<u64>> for SegmentRangeInclusive {
336 fn from(value: RangeInclusive<u64>) -> Self {
337 Self { start: *value.start(), end: *value.end() }
338 }
339}
340
341impl From<&SegmentRangeInclusive> for RangeInclusive<u64> {
342 fn from(value: &SegmentRangeInclusive) -> Self {
343 value.start()..=value.end()
344 }
345}
346
347impl From<SegmentRangeInclusive> for RangeInclusive<u64> {
348 fn from(value: SegmentRangeInclusive) -> Self {
349 (&value).into()
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_filename() {
359 let test_vectors = [
360 (StaticFileSegment::Headers, 2..=30, "static_file_headers_2_30", None),
361 (StaticFileSegment::Receipts, 30..=300, "static_file_receipts_30_300", None),
362 (
363 StaticFileSegment::Transactions,
364 1_123_233..=11_223_233,
365 "static_file_transactions_1123233_11223233",
366 None,
367 ),
368 (
369 StaticFileSegment::Headers,
370 2..=30,
371 "static_file_headers_2_30_none_lz4",
372 Some(Compression::Lz4),
373 ),
374 (
375 StaticFileSegment::Headers,
376 2..=30,
377 "static_file_headers_2_30_none_zstd",
378 Some(Compression::Zstd),
379 ),
380 (
381 StaticFileSegment::Headers,
382 2..=30,
383 "static_file_headers_2_30_none_zstd-dict",
384 Some(Compression::ZstdWithDictionary),
385 ),
386 ];
387
388 for (segment, block_range, filename, compression) in test_vectors {
389 let block_range: SegmentRangeInclusive = block_range.into();
390 if let Some(compression) = compression {
391 assert_eq!(
392 segment.filename_with_configuration(compression, &block_range),
393 filename
394 );
395 } else {
396 assert_eq!(segment.filename(&block_range), filename);
397 }
398
399 assert_eq!(StaticFileSegment::parse_filename(filename), Some((segment, block_range)));
400 }
401
402 assert_eq!(StaticFileSegment::parse_filename("static_file_headers_2"), None);
403 assert_eq!(StaticFileSegment::parse_filename("static_file_headers_"), None);
404 }
405}