reth_provider/providers/database/
save_blocks.rs1use alloy_consensus::BlockHeader;
2use alloy_eips::BlockNumHash;
3use alloy_primitives::BlockNumber;
4use reth_chain_state::ExecutedBlock;
5use reth_ethereum_primitives::EthPrimitives;
6use reth_primitives_traits::NodePrimitives;
7
8#[derive(Debug)]
33pub struct SaveBlocksInput<N: NodePrimitives = EthPrimitives> {
34 blocks: Vec<ExecutedBlock<N>>,
35 prev_db_tip: BlockNumber,
36 prev_partial_state_trie: BlockNumber,
37 new_db_tip: BlockNumber,
38 new_partial_state_trie: BlockNumber,
39}
40
41impl<N: NodePrimitives> SaveBlocksInput<N> {
42 pub fn new(
55 blocks: Vec<ExecutedBlock<N>>,
56 prev_db_tip: BlockNumber,
57 prev_partial_state_trie: BlockNumber,
58 new_db_tip: BlockNumber,
59 new_partial_state_trie: BlockNumber,
60 ) -> Self {
61 assert!(
62 prev_partial_state_trie <= prev_db_tip,
63 "previous state/trie tip must not exceed previous database tip"
64 );
65 assert!(prev_db_tip <= new_db_tip, "database tip must not move backwards");
66 assert!(
67 prev_partial_state_trie <= new_partial_state_trie,
68 "state/trie tip must not move backwards"
69 );
70 assert!(
71 prev_db_tip < new_db_tip || prev_partial_state_trie < new_partial_state_trie,
72 "at least one persistence frontier must advance"
73 );
74 assert!(
75 new_partial_state_trie <= new_db_tip,
76 "new state/trie tip must not exceed new database tip"
77 );
78
79 let expected_len = new_db_tip.saturating_sub(prev_partial_state_trie) as usize;
80 assert_eq!(
81 blocks.len(),
82 expected_len,
83 "blocks must cover (prev_partial_state_trie, new_db_tip]"
84 );
85 assert!(
86 blocks.iter().enumerate().all(|(index, block)| {
87 block.recovered_block().number() == prev_partial_state_trie + index as u64 + 1
88 }),
89 "blocks must cover (prev_partial_state_trie, new_db_tip]"
90 );
91
92 Self { blocks, prev_db_tip, prev_partial_state_trie, new_db_tip, new_partial_state_trie }
93 }
94
95 pub const fn prev_db_tip(&self) -> BlockNumber {
97 self.prev_db_tip
98 }
99
100 pub const fn prev_partial_state_trie(&self) -> BlockNumber {
102 self.prev_partial_state_trie
103 }
104
105 pub const fn new_db_tip(&self) -> BlockNumber {
107 self.new_db_tip
108 }
109
110 pub const fn new_partial_state_trie(&self) -> BlockNumber {
112 self.new_partial_state_trie
113 }
114
115 pub fn last_block(&self) -> BlockNumHash {
117 self.blocks
118 .last()
119 .expect("constructor ensures a non-empty block range")
120 .recovered_block()
121 .num_hash()
122 }
123
124 pub fn first_persist_rest_block(&self) -> Option<&ExecutedBlock<N>> {
126 self.persist_rest_blocks().first()
127 }
128
129 pub fn persist_rest_blocks(&self) -> &[ExecutedBlock<N>] {
131 &self.blocks[(self.prev_db_tip - self.prev_partial_state_trie) as usize..]
132 }
133
134 pub fn state_trie_blocks(&self) -> &[ExecutedBlock<N>] {
138 &self.blocks[..(self.new_partial_state_trie - self.prev_partial_state_trie) as usize]
139 }
140
141 pub fn state_trie_masking_blocks(&self) -> &[ExecutedBlock<N>] {
146 &self.blocks[(self.new_partial_state_trie - self.prev_partial_state_trie) as usize..]
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use reth_chain_state::test_utils::TestBlockBuilder;
154
155 #[test]
156 fn splits_frontier_ranges() {
157 let blocks = TestBlockBuilder::eth().get_executed_blocks(6..23).collect();
158 let input = SaveBlocksInput::new(blocks, 11, 5, 22, 16);
159
160 assert_eq!(
161 input
162 .persist_rest_blocks()
163 .iter()
164 .map(|block| block.recovered_block().number())
165 .collect::<Vec<_>>(),
166 (12..=22).collect::<Vec<_>>()
167 );
168 assert_eq!(
169 input
170 .state_trie_blocks()
171 .iter()
172 .map(|block| block.recovered_block().number())
173 .collect::<Vec<_>>(),
174 (6..=16).collect::<Vec<_>>()
175 );
176 assert_eq!(
177 input
178 .state_trie_masking_blocks()
179 .iter()
180 .map(|block| block.recovered_block().number())
181 .collect::<Vec<_>>(),
182 (17..=22).collect::<Vec<_>>()
183 );
184 }
185
186 #[test]
187 fn grows_masking_window_without_forcing_new_state_writes() {
188 let blocks = TestBlockBuilder::eth().get_executed_blocks(13..17).collect();
189 let input = SaveBlocksInput::new(blocks, 15, 12, 16, 13);
190
191 assert_eq!(input.first_persist_rest_block().unwrap().recovered_block().number(), 16);
192 assert_eq!(input.state_trie_blocks()[0].recovered_block().number(), 13);
193 assert_eq!(
194 input
195 .state_trie_masking_blocks()
196 .iter()
197 .map(|block| block.recovered_block().number())
198 .collect::<Vec<_>>(),
199 vec![14, 15, 16]
200 );
201 }
202
203 #[test]
204 fn advances_only_state_trie_frontier() {
205 let blocks = TestBlockBuilder::eth().get_executed_blocks(1..2).collect();
206 let input = SaveBlocksInput::new(blocks, 1, 0, 1, 1);
207
208 assert!(input.persist_rest_blocks().is_empty());
209 assert!(input.first_persist_rest_block().is_none());
210 assert_eq!(input.state_trie_blocks()[0].recovered_block().number(), 1);
211 assert!(input.state_trie_masking_blocks().is_empty());
212 }
213
214 #[test]
215 #[should_panic(expected = "at least one persistence frontier must advance")]
216 fn requires_a_frontier_to_advance() {
217 let _ = SaveBlocksInput::<EthPrimitives>::new(vec![], 1, 1, 1, 1);
218 }
219
220 #[test]
221 #[should_panic(expected = "blocks must cover (prev_partial_state_trie, new_db_tip]")]
222 fn requires_contiguous_blocks() {
223 let blocks = TestBlockBuilder::eth().get_executed_blocks(2..4).collect();
224 let _ = SaveBlocksInput::new(blocks, 0, 0, 2, 2);
225 }
226}