1use super::StageId;
2#[cfg(test)]
3use alloc::vec;
4use alloc::{format, string::String, vec::Vec};
5use alloy_primitives::{Address, BlockNumber, B256, U256};
6use core::ops::RangeInclusive;
7use reth_trie_common::{hash_builder::HashBuilderState, StoredSubNode};
8
9#[derive(Default, Debug, Clone, PartialEq, Eq)]
11pub struct MerkleCheckpoint {
12 pub target_block: BlockNumber,
14 pub last_account_key: B256,
16 pub walker_stack: Vec<StoredSubNode>,
18 pub state: HashBuilderState,
20 pub storage_root_checkpoint: Option<StorageRootMerkleCheckpoint>,
22}
23
24impl MerkleCheckpoint {
25 pub const fn new(
27 target_block: BlockNumber,
28 last_account_key: B256,
29 walker_stack: Vec<StoredSubNode>,
30 state: HashBuilderState,
31 ) -> Self {
32 Self { target_block, last_account_key, walker_stack, state, storage_root_checkpoint: None }
33 }
34}
35
36#[cfg(any(test, feature = "reth-codec"))]
37impl reth_codecs::Compact for MerkleCheckpoint {
38 fn to_compact<B>(&self, buf: &mut B) -> usize
39 where
40 B: bytes::BufMut + AsMut<[u8]>,
41 {
42 let mut len = 0;
43
44 buf.put_u64(self.target_block);
45 len += 8;
46
47 buf.put_slice(self.last_account_key.as_slice());
48 len += self.last_account_key.len();
49
50 buf.put_u16(self.walker_stack.len() as u16);
51 len += 2;
52 for item in &self.walker_stack {
53 len += item.to_compact(buf);
54 }
55
56 len += self.state.to_compact(buf);
57
58 match &self.storage_root_checkpoint {
60 Some(checkpoint) => {
61 buf.put_u8(1);
63 len += 1;
64 len += checkpoint.to_compact(buf);
65 }
66 None => {
67 buf.put_u8(0);
69 len += 1;
70 }
71 }
72
73 len
74 }
75
76 fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
77 use bytes::Buf;
78 let target_block = buf.get_u64();
79
80 let last_account_key = B256::from_slice(&buf[..32]);
81 buf.advance(32);
82
83 let walker_stack_len = buf.get_u16() as usize;
84 let mut walker_stack = Vec::with_capacity(walker_stack_len);
85 for _ in 0..walker_stack_len {
86 let (item, rest) = StoredSubNode::from_compact(buf, 0);
87 walker_stack.push(item);
88 buf = rest;
89 }
90
91 let (state, mut buf) = HashBuilderState::from_compact(buf, 0);
92
93 let (storage_root_checkpoint, buf) = if buf.is_empty() {
95 (None, buf)
96 } else {
97 match buf.get_u8() {
98 1 => {
99 let (checkpoint, rest) = StorageRootMerkleCheckpoint::from_compact(buf, 0);
100 (Some(checkpoint), rest)
101 }
102 _ => (None, buf),
103 }
104 };
105
106 (Self { target_block, last_account_key, walker_stack, state, storage_root_checkpoint }, buf)
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct StorageRootMerkleCheckpoint {
115 pub last_storage_key: B256,
117 pub walker_stack: Vec<StoredSubNode>,
119 pub state: HashBuilderState,
121 pub account_nonce: u64,
123 pub account_balance: U256,
125 pub account_bytecode_hash: B256,
127}
128
129impl StorageRootMerkleCheckpoint {
130 pub const fn new(
132 last_storage_key: B256,
133 walker_stack: Vec<StoredSubNode>,
134 state: HashBuilderState,
135 account_nonce: u64,
136 account_balance: U256,
137 account_bytecode_hash: B256,
138 ) -> Self {
139 Self {
140 last_storage_key,
141 walker_stack,
142 state,
143 account_nonce,
144 account_balance,
145 account_bytecode_hash,
146 }
147 }
148}
149
150#[cfg(any(test, feature = "reth-codec"))]
151impl reth_codecs::Compact for StorageRootMerkleCheckpoint {
152 fn to_compact<B>(&self, buf: &mut B) -> usize
153 where
154 B: bytes::BufMut + AsMut<[u8]>,
155 {
156 let mut len = 0;
157
158 buf.put_slice(self.last_storage_key.as_slice());
159 len += self.last_storage_key.len();
160
161 buf.put_u16(self.walker_stack.len() as u16);
162 len += 2;
163 for item in &self.walker_stack {
164 len += item.to_compact(buf);
165 }
166
167 len += self.state.to_compact(buf);
168
169 buf.put_u64(self.account_nonce);
171 len += 8;
172
173 let balance_len = self.account_balance.byte_len() as u8;
174 buf.put_u8(balance_len);
175 len += 1;
176 len += self.account_balance.to_compact(buf);
177
178 buf.put_slice(self.account_bytecode_hash.as_slice());
179 len += 32;
180
181 len
182 }
183
184 fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
185 use bytes::Buf;
186
187 let last_storage_key = B256::from_slice(&buf[..32]);
188 buf.advance(32);
189
190 let walker_stack_len = buf.get_u16() as usize;
191 let mut walker_stack = Vec::with_capacity(walker_stack_len);
192 for _ in 0..walker_stack_len {
193 let (item, rest) = StoredSubNode::from_compact(buf, 0);
194 walker_stack.push(item);
195 buf = rest;
196 }
197
198 let (state, mut buf) = HashBuilderState::from_compact(buf, 0);
199
200 let account_nonce = buf.get_u64();
202 let balance_len = buf.get_u8() as usize;
203 let (account_balance, mut buf) = U256::from_compact(buf, balance_len);
204 let account_bytecode_hash = B256::from_slice(&buf[..32]);
205 buf.advance(32);
206
207 (
208 Self {
209 last_storage_key,
210 walker_stack,
211 state,
212 account_nonce,
213 account_balance,
214 account_bytecode_hash,
215 },
216 buf,
217 )
218 }
219}
220
221#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
223#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
224#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
225#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
226#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
227pub struct AccountHashingCheckpoint {
228 pub address: Option<Address>,
230 pub block_range: CheckpointBlockRange,
232 pub progress: EntitiesCheckpoint,
234}
235
236#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
238#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
239#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
240#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
241#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
242pub struct StorageHashingCheckpoint {
243 pub address: Option<Address>,
245 pub storage: Option<B256>,
247 pub block_range: CheckpointBlockRange,
249 pub progress: EntitiesCheckpoint,
251}
252
253#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
255#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
256#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
257#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259pub struct ExecutionCheckpoint {
260 pub block_range: CheckpointBlockRange,
262 pub progress: EntitiesCheckpoint,
264}
265
266#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
268#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
269#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
270#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
271#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
272pub struct HeadersCheckpoint {
273 pub block_range: CheckpointBlockRange,
275 pub progress: EntitiesCheckpoint,
277}
278
279#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
281#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
282#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
283#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
284#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285pub struct IndexHistoryCheckpoint {
286 pub block_range: CheckpointBlockRange,
288 pub progress: EntitiesCheckpoint,
290}
291
292#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
297#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
298#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
299#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
300#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
301pub struct MerkleChangeSetsCheckpoint {
302 pub block_range: CheckpointBlockRange,
304}
305
306#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
308#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
309#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
310#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312pub struct EntitiesCheckpoint {
313 pub processed: u64,
315 pub total: u64,
317}
318
319impl EntitiesCheckpoint {
320 pub fn fmt_percentage(&self) -> Option<String> {
324 if self.total == 0 {
325 return None
326 }
327
328 let percentage = 100.0 * self.processed as f64 / self.total as f64;
330
331 #[cfg(not(feature = "std"))]
333 {
334 let scaled = (percentage * 100.0) as u64;
336 Some(format!("{:.2}%", scaled as f64 / 100.0))
337 }
338 #[cfg(feature = "std")]
339 Some(format!("{:.2}%", (percentage * 100.0).floor() / 100.0))
340 }
341}
342
343#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
346#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
347#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
348#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
349#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
350pub struct CheckpointBlockRange {
351 pub from: BlockNumber,
353 pub to: BlockNumber,
355}
356
357impl From<RangeInclusive<BlockNumber>> for CheckpointBlockRange {
358 fn from(range: RangeInclusive<BlockNumber>) -> Self {
359 Self { from: *range.start(), to: *range.end() }
360 }
361}
362
363impl From<&RangeInclusive<BlockNumber>> for CheckpointBlockRange {
364 fn from(range: &RangeInclusive<BlockNumber>) -> Self {
365 Self { from: *range.start(), to: *range.end() }
366 }
367}
368
369#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
371#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
372#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
373#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
374#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
375pub struct StageCheckpoint {
376 pub block_number: BlockNumber,
378 pub stage_checkpoint: Option<StageUnitCheckpoint>,
380}
381
382impl StageCheckpoint {
383 pub fn new(block_number: BlockNumber) -> Self {
385 Self { block_number, ..Default::default() }
386 }
387
388 pub const fn with_block_number(mut self, block_number: BlockNumber) -> Self {
390 self.block_number = block_number;
391 self
392 }
393
394 pub fn with_block_range(mut self, stage_id: &StageId, from: u64, to: u64) -> Self {
396 self.stage_checkpoint = Some(match stage_id {
397 StageId::Execution => StageUnitCheckpoint::Execution(ExecutionCheckpoint::default()),
398 StageId::AccountHashing => {
399 StageUnitCheckpoint::Account(AccountHashingCheckpoint::default())
400 }
401 StageId::StorageHashing => {
402 StageUnitCheckpoint::Storage(StorageHashingCheckpoint::default())
403 }
404 StageId::IndexStorageHistory | StageId::IndexAccountHistory => {
405 StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint::default())
406 }
407 _ => return self,
408 });
409 _ = self.stage_checkpoint.map(|mut checkpoint| checkpoint.set_block_range(from, to));
410 self
411 }
412
413 pub fn entities(&self) -> Option<EntitiesCheckpoint> {
416 let stage_checkpoint = self.stage_checkpoint?;
417
418 match stage_checkpoint {
419 StageUnitCheckpoint::Account(AccountHashingCheckpoint {
420 progress: entities, ..
421 }) |
422 StageUnitCheckpoint::Storage(StorageHashingCheckpoint {
423 progress: entities, ..
424 }) |
425 StageUnitCheckpoint::Entities(entities) |
426 StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) |
427 StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) |
428 StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint {
429 progress: entities,
430 ..
431 }) => Some(entities),
432 StageUnitCheckpoint::MerkleChangeSets(_) => None,
433 }
434 }
435}
436
437#[derive(Debug, PartialEq, Eq, Clone, Copy)]
441#[cfg_attr(any(test, feature = "test-utils"), derive(arbitrary::Arbitrary))]
442#[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]
443#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
444#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
445pub enum StageUnitCheckpoint {
446 Account(AccountHashingCheckpoint),
448 Storage(StorageHashingCheckpoint),
450 Entities(EntitiesCheckpoint),
452 Execution(ExecutionCheckpoint),
454 Headers(HeadersCheckpoint),
456 IndexHistory(IndexHistoryCheckpoint),
458 MerkleChangeSets(MerkleChangeSetsCheckpoint),
463}
464
465impl StageUnitCheckpoint {
466 pub const fn set_block_range(&mut self, from: u64, to: u64) -> Option<CheckpointBlockRange> {
469 match self {
470 Self::Account(AccountHashingCheckpoint { block_range, .. }) |
471 Self::Storage(StorageHashingCheckpoint { block_range, .. }) |
472 Self::Execution(ExecutionCheckpoint { block_range, .. }) |
473 Self::IndexHistory(IndexHistoryCheckpoint { block_range, .. }) => {
474 let old_range = *block_range;
475 *block_range = CheckpointBlockRange { from, to };
476
477 Some(old_range)
478 }
479 _ => None,
480 }
481 }
482}
483
484#[cfg(test)]
485impl Default for StageUnitCheckpoint {
486 fn default() -> Self {
487 Self::Account(AccountHashingCheckpoint::default())
488 }
489}
490
491macro_rules! stage_unit_checkpoints {
493 ($(($index:expr,$enum_variant:tt,$checkpoint_ty:ty,#[doc = $fn_get_doc:expr]$fn_get_name:ident,#[doc = $fn_build_doc:expr]$fn_build_name:ident)),+) => {
494 impl StageCheckpoint {
495 $(
496 #[doc = $fn_get_doc]
497 pub const fn $fn_get_name(&self) -> Option<$checkpoint_ty> {
498 match self.stage_checkpoint {
499 Some(StageUnitCheckpoint::$enum_variant(checkpoint)) => Some(checkpoint),
500 _ => None,
501 }
502 }
503
504 #[doc = $fn_build_doc]
505 pub const fn $fn_build_name(
506 mut self,
507 checkpoint: $checkpoint_ty,
508 ) -> Self {
509 self.stage_checkpoint = Some(StageUnitCheckpoint::$enum_variant(checkpoint));
510 self
511 }
512 )+
513 }
514 };
515}
516
517stage_unit_checkpoints!(
518 (
519 0,
520 Account,
521 AccountHashingCheckpoint,
522 account_hashing_stage_checkpoint,
524 with_account_hashing_stage_checkpoint
526 ),
527 (
528 1,
529 Storage,
530 StorageHashingCheckpoint,
531 storage_hashing_stage_checkpoint,
533 with_storage_hashing_stage_checkpoint
535 ),
536 (
537 2,
538 Entities,
539 EntitiesCheckpoint,
540 entities_stage_checkpoint,
542 with_entities_stage_checkpoint
544 ),
545 (
546 3,
547 Execution,
548 ExecutionCheckpoint,
549 execution_stage_checkpoint,
551 with_execution_stage_checkpoint
553 ),
554 (
555 4,
556 Headers,
557 HeadersCheckpoint,
558 headers_stage_checkpoint,
560 with_headers_stage_checkpoint
562 ),
563 (
564 5,
565 IndexHistory,
566 IndexHistoryCheckpoint,
567 index_history_stage_checkpoint,
569 with_index_history_stage_checkpoint
571 )
572);
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use alloy_primitives::b256;
578 use rand::Rng;
579 use reth_codecs::Compact;
580
581 #[test]
582 fn merkle_checkpoint_roundtrip() {
583 let mut rng = rand::rng();
584 let checkpoint = MerkleCheckpoint {
585 target_block: rng.random(),
586 last_account_key: rng.random(),
587 walker_stack: vec![StoredSubNode {
588 key: B256::random_with(&mut rng).to_vec(),
589 nibble: Some(rng.random()),
590 node: None,
591 }],
592 state: HashBuilderState::default(),
593 storage_root_checkpoint: None,
594 };
595
596 let mut buf = Vec::new();
597 let encoded = checkpoint.to_compact(&mut buf);
598 let (decoded, _) = MerkleCheckpoint::from_compact(&buf, encoded);
599 assert_eq!(decoded, checkpoint);
600 }
601
602 #[test]
603 fn storage_root_merkle_checkpoint_roundtrip() {
604 let mut rng = rand::rng();
605 let checkpoint = StorageRootMerkleCheckpoint {
606 last_storage_key: rng.random(),
607 walker_stack: vec![StoredSubNode {
608 key: B256::random_with(&mut rng).to_vec(),
609 nibble: Some(rng.random()),
610 node: None,
611 }],
612 state: HashBuilderState::default(),
613 account_nonce: 0,
614 account_balance: U256::ZERO,
615 account_bytecode_hash: B256::ZERO,
616 };
617
618 let mut buf = Vec::new();
619 let encoded = checkpoint.to_compact(&mut buf);
620 let (decoded, _) = StorageRootMerkleCheckpoint::from_compact(&buf, encoded);
621 assert_eq!(decoded, checkpoint);
622 }
623
624 #[test]
625 fn merkle_checkpoint_with_storage_root_roundtrip() {
626 let mut rng = rand::rng();
627
628 let storage_checkpoint = StorageRootMerkleCheckpoint {
630 last_storage_key: rng.random(),
631 walker_stack: vec![StoredSubNode {
632 key: B256::random_with(&mut rng).to_vec(),
633 nibble: Some(rng.random()),
634 node: None,
635 }],
636 state: HashBuilderState::default(),
637 account_nonce: 1,
638 account_balance: U256::from(1),
639 account_bytecode_hash: b256!(
640 "0x0fffffffffffffffffffffffffffffff0fffffffffffffffffffffffffffffff"
641 ),
642 };
643
644 let checkpoint = MerkleCheckpoint {
646 target_block: rng.random(),
647 last_account_key: rng.random(),
648 walker_stack: vec![StoredSubNode {
649 key: B256::random_with(&mut rng).to_vec(),
650 nibble: Some(rng.random()),
651 node: None,
652 }],
653 state: HashBuilderState::default(),
654 storage_root_checkpoint: Some(storage_checkpoint),
655 };
656
657 let mut buf = Vec::new();
658 let encoded = checkpoint.to_compact(&mut buf);
659 let (decoded, _) = MerkleCheckpoint::from_compact(&buf, encoded);
660 assert_eq!(decoded, checkpoint);
661 }
662}