1use crate::{BlockNumber, Compression};
2use alloc::{
3 format,
4 string::{String, ToString},
5};
6use alloy_primitives::TxNumber;
7use core::{ops::RangeInclusive, str::FromStr};
8use derive_more::Display;
9use serde::{Deserialize, Serialize};
10use strum::{AsRefStr, EnumString};
11
12#[derive(
13 Debug,
14 Copy,
15 Clone,
16 Eq,
17 PartialEq,
18 Hash,
19 Ord,
20 PartialOrd,
21 Deserialize,
22 Serialize,
23 EnumString,
24 AsRefStr,
25 Display,
26)]
27#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
28pub enum StaticFileSegment {
30 #[strum(serialize = "headers")]
31 Headers,
34 #[strum(serialize = "transactions")]
35 Transactions,
37 #[strum(serialize = "receipts")]
38 Receipts,
40}
41
42impl StaticFileSegment {
43 pub const fn as_str(&self) -> &'static str {
45 match self {
46 Self::Headers => "headers",
47 Self::Transactions => "transactions",
48 Self::Receipts => "receipts",
49 }
50 }
51
52 pub fn iter() -> impl Iterator<Item = Self> {
54 [Self::Headers, Self::Transactions, Self::Receipts].into_iter()
56 }
57
58 pub const fn config(&self) -> SegmentConfig {
60 SegmentConfig { compression: Compression::Lz4 }
61 }
62
63 pub const fn columns(&self) -> usize {
65 match self {
66 Self::Headers => 3,
67 Self::Transactions | Self::Receipts => 1,
68 }
69 }
70
71 pub fn filename(&self, block_range: &SegmentRangeInclusive) -> String {
73 format!("static_file_{}_{}_{}", self.as_ref(), block_range.start(), block_range.end())
76 }
77
78 pub fn filename_with_configuration(
80 &self,
81 compression: Compression,
82 block_range: &SegmentRangeInclusive,
83 ) -> String {
84 let prefix = self.filename(block_range);
85
86 let filters_name = "none".to_string();
87
88 format!("{prefix}_{}_{}", filters_name, compression.as_ref())
91 }
92
93 pub fn parse_filename(name: &str) -> Option<(Self, SegmentRangeInclusive)> {
110 let mut parts = name.split('_');
111 if !(parts.next() == Some("static") && parts.next() == Some("file")) {
112 return None
113 }
114
115 let segment = Self::from_str(parts.next()?).ok()?;
116 let (block_start, block_end) = (parts.next()?.parse().ok()?, parts.next()?.parse().ok()?);
117
118 if block_start > block_end {
119 return None
120 }
121
122 Some((segment, SegmentRangeInclusive::new(block_start, block_end)))
123 }
124
125 pub const fn is_headers(&self) -> bool {
127 matches!(self, Self::Headers)
128 }
129
130 pub const fn is_receipts(&self) -> bool {
132 matches!(self, Self::Receipts)
133 }
134
135 pub const fn is_tx_based(&self) -> bool {
137 matches!(self, Self::Receipts | Self::Transactions)
138 }
139
140 pub const fn is_block_based(&self) -> bool {
142 matches!(self, Self::Headers)
143 }
144}
145
146#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
148pub struct SegmentHeader {
149 expected_block_range: SegmentRangeInclusive,
154 block_range: Option<SegmentRangeInclusive>,
156 tx_range: Option<SegmentRangeInclusive>,
158 segment: StaticFileSegment,
160}
161
162impl SegmentHeader {
163 pub const fn new(
165 expected_block_range: SegmentRangeInclusive,
166 block_range: Option<SegmentRangeInclusive>,
167 tx_range: Option<SegmentRangeInclusive>,
168 segment: StaticFileSegment,
169 ) -> Self {
170 Self { expected_block_range, block_range, tx_range, segment }
171 }
172
173 pub const fn segment(&self) -> StaticFileSegment {
175 self.segment
176 }
177
178 pub const fn block_range(&self) -> Option<&SegmentRangeInclusive> {
180 self.block_range.as_ref()
181 }
182
183 pub const fn tx_range(&self) -> Option<&SegmentRangeInclusive> {
185 self.tx_range.as_ref()
186 }
187
188 pub const fn expected_block_start(&self) -> BlockNumber {
190 self.expected_block_range.start()
191 }
192
193 pub const fn expected_block_end(&self) -> BlockNumber {
195 self.expected_block_range.end()
196 }
197
198 pub fn block_start(&self) -> Option<BlockNumber> {
200 self.block_range.as_ref().map(|b| b.start())
201 }
202
203 pub fn block_end(&self) -> Option<BlockNumber> {
205 self.block_range.as_ref().map(|b| b.end())
206 }
207
208 pub fn tx_start(&self) -> Option<TxNumber> {
210 self.tx_range.as_ref().map(|t| t.start())
211 }
212
213 pub fn tx_end(&self) -> Option<TxNumber> {
215 self.tx_range.as_ref().map(|t| t.end())
216 }
217
218 pub fn tx_len(&self) -> Option<u64> {
220 self.tx_range.as_ref().map(|r| r.len())
221 }
222
223 pub fn block_len(&self) -> Option<u64> {
225 self.block_range.as_ref().map(|r| r.len())
226 }
227
228 pub const fn increment_block(&mut self) -> BlockNumber {
230 if let Some(block_range) = &mut self.block_range {
231 block_range.end += 1;
232 block_range.end
233 } else {
234 self.block_range = Some(SegmentRangeInclusive::new(
235 self.expected_block_start(),
236 self.expected_block_start(),
237 ));
238 self.expected_block_start()
239 }
240 }
241
242 pub const fn increment_tx(&mut self) {
244 if self.segment.is_tx_based() {
245 if let Some(tx_range) = &mut self.tx_range {
246 tx_range.end += 1;
247 } else {
248 self.tx_range = Some(SegmentRangeInclusive::new(0, 0));
249 }
250 }
251 }
252
253 pub const fn prune(&mut self, num: u64) {
255 if self.segment.is_block_based() {
256 if let Some(range) = &mut self.block_range {
257 if num > range.end - range.start {
258 self.block_range = None;
259 } else {
260 range.end = range.end.saturating_sub(num);
261 }
262 };
263 } else if let Some(range) = &mut self.tx_range {
264 if num > range.end - range.start {
265 self.tx_range = None;
266 } else {
267 range.end = range.end.saturating_sub(num);
268 }
269 }
270 }
271
272 pub const fn set_block_range(&mut self, block_start: BlockNumber, block_end: BlockNumber) {
274 if let Some(block_range) = &mut self.block_range {
275 block_range.start = block_start;
276 block_range.end = block_end;
277 } else {
278 self.block_range = Some(SegmentRangeInclusive::new(block_start, block_end))
279 }
280 }
281
282 pub const fn set_tx_range(&mut self, tx_start: TxNumber, tx_end: TxNumber) {
284 if let Some(tx_range) = &mut self.tx_range {
285 tx_range.start = tx_start;
286 tx_range.end = tx_end;
287 } else {
288 self.tx_range = Some(SegmentRangeInclusive::new(tx_start, tx_end))
289 }
290 }
291
292 pub fn start(&self) -> Option<u64> {
294 if self.segment.is_block_based() {
295 return self.block_start()
296 }
297 self.tx_start()
298 }
299}
300
301#[derive(Debug, Clone, Copy)]
303pub struct SegmentConfig {
304 pub compression: Compression,
306}
307
308#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
312pub struct SegmentRangeInclusive {
313 start: u64,
314 end: u64,
315}
316
317impl SegmentRangeInclusive {
318 pub const fn new(start: u64, end: u64) -> Self {
320 Self { start, end }
321 }
322
323 pub const fn start(&self) -> u64 {
325 self.start
326 }
327
328 pub const fn end(&self) -> u64 {
330 self.end
331 }
332
333 #[allow(clippy::len_without_is_empty)]
335 pub const fn len(&self) -> u64 {
336 self.end.saturating_sub(self.start).saturating_add(1)
337 }
338}
339
340impl core::fmt::Display for SegmentRangeInclusive {
341 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342 write!(f, "{}..={}", self.start, self.end)
343 }
344}
345
346impl From<RangeInclusive<u64>> for SegmentRangeInclusive {
347 fn from(value: RangeInclusive<u64>) -> Self {
348 Self { start: *value.start(), end: *value.end() }
349 }
350}
351
352impl From<&SegmentRangeInclusive> for RangeInclusive<u64> {
353 fn from(value: &SegmentRangeInclusive) -> Self {
354 value.start()..=value.end()
355 }
356}
357
358impl From<SegmentRangeInclusive> for RangeInclusive<u64> {
359 fn from(value: SegmentRangeInclusive) -> Self {
360 (&value).into()
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use alloy_primitives::hex;
368 use reth_nippy_jar::NippyJar;
369
370 #[test]
371 fn test_filename() {
372 let test_vectors = [
373 (StaticFileSegment::Headers, 2..=30, "static_file_headers_2_30", None),
374 (StaticFileSegment::Receipts, 30..=300, "static_file_receipts_30_300", None),
375 (
376 StaticFileSegment::Transactions,
377 1_123_233..=11_223_233,
378 "static_file_transactions_1123233_11223233",
379 None,
380 ),
381 (
382 StaticFileSegment::Headers,
383 2..=30,
384 "static_file_headers_2_30_none_lz4",
385 Some(Compression::Lz4),
386 ),
387 (
388 StaticFileSegment::Headers,
389 2..=30,
390 "static_file_headers_2_30_none_zstd",
391 Some(Compression::Zstd),
392 ),
393 (
394 StaticFileSegment::Headers,
395 2..=30,
396 "static_file_headers_2_30_none_zstd-dict",
397 Some(Compression::ZstdWithDictionary),
398 ),
399 ];
400
401 for (segment, block_range, filename, compression) in test_vectors {
402 let block_range: SegmentRangeInclusive = block_range.into();
403 if let Some(compression) = compression {
404 assert_eq!(
405 segment.filename_with_configuration(compression, &block_range),
406 filename
407 );
408 } else {
409 assert_eq!(segment.filename(&block_range), filename);
410 }
411
412 assert_eq!(StaticFileSegment::parse_filename(filename), Some((segment, block_range)));
413 }
414
415 assert_eq!(StaticFileSegment::parse_filename("static_file_headers_2"), None);
416 assert_eq!(StaticFileSegment::parse_filename("static_file_headers_"), None);
417
418 let dummy_range = SegmentRangeInclusive::new(123, 1230);
420 for segment in StaticFileSegment::iter() {
421 let filename = segment.filename(&dummy_range);
422 assert_eq!(Some((segment, dummy_range)), StaticFileSegment::parse_filename(&filename));
423 }
424 }
425
426 #[test]
427 fn test_segment_config_backwards() {
428 let headers = hex!(
429 "010000000000000000000000000000001fa10700000000000100000000000000001fa10700000000000000000000030000000000000020a107000000000001010000004a02000000000000"
430 );
431 let transactions = hex!(
432 "010000000000000000000000000000001fa10700000000000100000000000000001fa107000000000001000000000000000034a107000000000001000000010000000000000035a1070000000000004010000000000000"
433 );
434 let receipts = hex!(
435 "010000000000000000000000000000001fa10700000000000100000000000000000000000000000000000200000001000000000000000000000000000000000000000000000000"
436 );
437
438 {
439 let headers = NippyJar::<SegmentHeader>::load_from_reader(&headers[..]).unwrap();
440 assert_eq!(
441 &SegmentHeader {
442 expected_block_range: SegmentRangeInclusive::new(0, 499999),
443 block_range: Some(SegmentRangeInclusive::new(0, 499999)),
444 tx_range: None,
445 segment: StaticFileSegment::Headers,
446 },
447 headers.user_header()
448 );
449 }
450 {
451 let transactions =
452 NippyJar::<SegmentHeader>::load_from_reader(&transactions[..]).unwrap();
453 assert_eq!(
454 &SegmentHeader {
455 expected_block_range: SegmentRangeInclusive::new(0, 499999),
456 block_range: Some(SegmentRangeInclusive::new(0, 499999)),
457 tx_range: Some(SegmentRangeInclusive::new(0, 500020)),
458 segment: StaticFileSegment::Transactions,
459 },
460 transactions.user_header()
461 );
462 }
463 {
464 let receipts = NippyJar::<SegmentHeader>::load_from_reader(&receipts[..]).unwrap();
465 assert_eq!(
466 &SegmentHeader {
467 expected_block_range: SegmentRangeInclusive::new(0, 499999),
468 block_range: Some(SegmentRangeInclusive::new(0, 0)),
469 tx_range: None,
470 segment: StaticFileSegment::Receipts,
471 },
472 receipts.user_header()
473 );
474 }
475 }
476}