reth_provider/providers/
consistent_view.rs1use crate::{BlockNumReader, DatabaseProviderFactory, HeaderProvider};
2use alloy_primitives::B256;
3pub use reth_storage_errors::provider::ConsistentViewError;
4use reth_storage_errors::provider::ProviderResult;
5
6#[derive(Clone, Debug)]
22pub struct ConsistentDbView<Factory> {
23 factory: Factory,
24 tip: Option<(B256, u64)>,
25}
26
27impl<Factory> ConsistentDbView<Factory>
28where
29 Factory: DatabaseProviderFactory<Provider: BlockNumReader + HeaderProvider>,
30{
31 pub const fn new(factory: Factory, tip: Option<(B256, u64)>) -> Self {
33 Self { factory, tip }
34 }
35
36 pub fn new_with_latest_tip(provider: Factory) -> ProviderResult<Self> {
38 let provider_ro = provider.database_provider_ro()?;
39 let last_num = provider_ro.last_block_number()?;
40 let tip = provider_ro.sealed_header(last_num)?.map(|h| (h.hash(), last_num));
41 Ok(Self::new(provider, tip))
42 }
43
44 pub fn provider_ro(&self) -> ProviderResult<Factory::Provider> {
46 let provider_ro = self.factory.database_provider_ro()?;
48
49 if let Some((hash, number)) = self.tip {
71 if provider_ro.sealed_header(number)?.is_none_or(|header| header.hash() != hash) {
72 return Err(ConsistentViewError::Reorged { block: hash }.into())
73 }
74 }
75
76 Ok(provider_ro)
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use reth_errors::ProviderError;
83 use std::str::FromStr;
84
85 use super::*;
86 use crate::{
87 test_utils::create_test_provider_factory_with_chain_spec, BlockWriter,
88 StaticFileProviderFactory, StaticFileWriter,
89 };
90 use alloy_primitives::Bytes;
91 use assert_matches::assert_matches;
92 use reth_chainspec::{EthChainSpec, MAINNET};
93 use reth_ethereum_primitives::{Block, BlockBody};
94 use reth_primitives_traits::{block::TestBlock, RecoveredBlock, SealedBlock};
95 use reth_static_file_types::StaticFileSegment;
96 use reth_storage_api::StorageLocation;
97
98 #[test]
99 fn test_consistent_view_extend() {
100 let provider_factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
101
102 let genesis_header = MAINNET.genesis_header();
103 let genesis_block =
104 SealedBlock::<Block>::seal_parts(genesis_header.clone(), BlockBody::default());
105 let genesis_hash: B256 = genesis_block.hash();
106 let genesis_block = RecoveredBlock::new_sealed(genesis_block, vec![]);
107
108 let provider_rw = provider_factory.provider_rw().unwrap();
110 provider_rw.insert_block(genesis_block, StorageLocation::StaticFiles).unwrap();
111 provider_rw.commit().unwrap();
112
113 let view = ConsistentDbView::new_with_latest_tip(provider_factory.clone()).unwrap();
115
116 assert_matches!(view.provider_ro(), Ok(_));
118
119 let mut block = Block::default();
121 block.header_mut().parent_hash = genesis_hash;
122 block.header_mut().number = 1;
123 let sealed_block = SealedBlock::seal_slow(block);
124 let recovered_block = RecoveredBlock::new_sealed(sealed_block, vec![]);
125
126 let provider_rw = provider_factory.provider_rw().unwrap();
128 provider_rw.insert_block(recovered_block, StorageLocation::StaticFiles).unwrap();
129 provider_rw.commit().unwrap();
130
131 assert_matches!(view.provider_ro(), Ok(_));
133
134 let mut block = Block::default();
136 block.header_mut().parent_hash = genesis_hash;
137 block.header_mut().number = 2;
138 let sealed_block = SealedBlock::seal_slow(block);
139 let recovered_block = RecoveredBlock::new_sealed(sealed_block, vec![]);
140
141 let provider_rw = provider_factory.provider_rw().unwrap();
143 provider_rw.insert_block(recovered_block, StorageLocation::StaticFiles).unwrap();
144 provider_rw.commit().unwrap();
145
146 assert_matches!(view.provider_ro(), Ok(_));
148 }
149
150 #[test]
151 fn test_consistent_view_remove() {
152 let provider_factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
153
154 let genesis_header = MAINNET.genesis_header();
155 let genesis_block =
156 SealedBlock::<Block>::seal_parts(genesis_header.clone(), BlockBody::default());
157 let genesis_hash: B256 = genesis_block.hash();
158 let genesis_block = RecoveredBlock::new_sealed(genesis_block, vec![]);
159
160 let provider_rw = provider_factory.provider_rw().unwrap();
162 provider_rw.insert_block(genesis_block, StorageLocation::Both).unwrap();
163 provider_rw.0.static_file_provider().commit().unwrap();
164 provider_rw.commit().unwrap();
165
166 let view = ConsistentDbView::new_with_latest_tip(provider_factory.clone()).unwrap();
168
169 assert_matches!(view.provider_ro(), Ok(_));
171
172 let mut block = Block::default();
174 block.header_mut().parent_hash = genesis_hash;
175 block.header_mut().number = 1;
176 let sealed_block = SealedBlock::seal_slow(block);
177 let recovered_block = RecoveredBlock::new_sealed(sealed_block.clone(), vec![]);
178
179 let provider_rw = provider_factory.provider_rw().unwrap();
181 provider_rw.insert_block(recovered_block, StorageLocation::Both).unwrap();
182 provider_rw.0.static_file_provider().commit().unwrap();
183 provider_rw.commit().unwrap();
184
185 let view = ConsistentDbView::new_with_latest_tip(provider_factory.clone()).unwrap();
187 let initial_tip_hash = sealed_block.hash();
188
189 assert_matches!(view.provider_ro(), Ok(_));
191
192 let provider_rw = provider_factory.provider_rw().unwrap();
194 provider_rw.remove_blocks_above(0, StorageLocation::Both).unwrap();
195 let sf_provider = provider_rw.0.static_file_provider();
196 sf_provider.get_writer(1, StaticFileSegment::Headers).unwrap().prune_headers(1).unwrap();
197 sf_provider.commit().unwrap();
198 provider_rw.commit().unwrap();
199
200 let Err(ProviderError::ConsistentView(boxed_consistent_view_err)) = view.provider_ro()
202 else {
203 panic!("expected reorged consistent view error, got success");
204 };
205 let unboxed = *boxed_consistent_view_err;
206 assert_eq!(unboxed, ConsistentViewError::Reorged { block: initial_tip_hash });
207
208 let mut block = Block::default();
210 block.header_mut().parent_hash = genesis_hash;
211 block.header_mut().number = 1;
212 block.header_mut().extra_data =
213 Bytes::from_str("6a6f75726e657920746f20697468616361").unwrap();
214 let sealed_block = SealedBlock::seal_slow(block);
215 let recovered_block = RecoveredBlock::new_sealed(sealed_block, vec![]);
216
217 let provider_rw = provider_factory.provider_rw().unwrap();
219 provider_rw.insert_block(recovered_block, StorageLocation::Both).unwrap();
220 provider_rw.0.static_file_provider().commit().unwrap();
221 provider_rw.commit().unwrap();
222
223 let Err(ProviderError::ConsistentView(boxed_consistent_view_err)) = view.provider_ro()
225 else {
226 panic!("expected reorged consistent view error, got success");
227 };
228 let unboxed = *boxed_consistent_view_err;
229 assert_eq!(unboxed, ConsistentViewError::Reorged { block: initial_tip_hash });
230 }
231}