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 provider_ro.sealed_header(number)?.is_none_or(|header| header.hash() != hash)
72 {
73 return Err(ConsistentViewError::Reorged { block: hash }.into())
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::{test_utils::create_test_provider_factory, BlockWriter};
87 use alloy_primitives::Bytes;
88 use assert_matches::assert_matches;
89 use reth_chainspec::{ChainSpecProvider, EthChainSpec};
90 use reth_ethereum_primitives::{Block, BlockBody};
91 use reth_primitives_traits::{block::TestBlock, RecoveredBlock, SealedBlock};
92
93 #[test]
94 fn test_consistent_view_extend() {
95 let provider_factory = create_test_provider_factory();
96
97 let genesis_block = SealedBlock::<Block>::seal_parts(
98 provider_factory.chain_spec().genesis_header().clone(),
99 BlockBody::default(),
100 );
101 let genesis_hash: B256 = genesis_block.hash();
102 let genesis_block = RecoveredBlock::new_sealed(genesis_block, vec![]);
103
104 let provider_rw = provider_factory.provider_rw().unwrap();
106 provider_rw.insert_block(genesis_block).unwrap();
107 provider_rw.commit().unwrap();
108
109 let view = ConsistentDbView::new_with_latest_tip(provider_factory.clone()).unwrap();
111
112 assert_matches!(view.provider_ro(), Ok(_));
114
115 let mut block = Block::default();
117 block.header_mut().parent_hash = genesis_hash;
118 block.header_mut().number = 1;
119 let sealed_block = SealedBlock::seal_slow(block);
120 let recovered_block = RecoveredBlock::new_sealed(sealed_block, vec![]);
121
122 let provider_rw = provider_factory.provider_rw().unwrap();
124 provider_rw.insert_block(recovered_block).unwrap();
125 provider_rw.commit().unwrap();
126
127 assert_matches!(view.provider_ro(), Ok(_));
129
130 let mut block = Block::default();
132 block.header_mut().parent_hash = genesis_hash;
133 block.header_mut().number = 2;
134 let sealed_block = SealedBlock::seal_slow(block);
135 let recovered_block = RecoveredBlock::new_sealed(sealed_block, vec![]);
136
137 let provider_rw = provider_factory.provider_rw().unwrap();
139 provider_rw.insert_block(recovered_block).unwrap();
140 provider_rw.commit().unwrap();
141
142 assert_matches!(view.provider_ro(), Ok(_));
144 }
145
146 #[test]
147 fn test_consistent_view_remove() {
148 let provider_factory = create_test_provider_factory();
149
150 let genesis_block = SealedBlock::<Block>::seal_parts(
151 provider_factory.chain_spec().genesis_header().clone(),
152 BlockBody::default(),
153 );
154 let genesis_hash: B256 = genesis_block.hash();
155 let genesis_block = RecoveredBlock::new_sealed(genesis_block, vec![]);
156
157 let provider_rw = provider_factory.provider_rw().unwrap();
159 provider_rw.insert_block(genesis_block).unwrap();
160 provider_rw.commit().unwrap();
161
162 let view = ConsistentDbView::new_with_latest_tip(provider_factory.clone()).unwrap();
164
165 assert_matches!(view.provider_ro(), Ok(_));
167
168 let mut block = Block::default();
170 block.header_mut().parent_hash = genesis_hash;
171 block.header_mut().number = 1;
172 let sealed_block = SealedBlock::seal_slow(block);
173 let recovered_block = RecoveredBlock::new_sealed(sealed_block.clone(), vec![]);
174
175 let provider_rw = provider_factory.provider_rw().unwrap();
177 provider_rw.insert_block(recovered_block).unwrap();
178 provider_rw.commit().unwrap();
179
180 let view = ConsistentDbView::new_with_latest_tip(provider_factory.clone()).unwrap();
182 let initial_tip_hash = sealed_block.hash();
183
184 assert_matches!(view.provider_ro(), Ok(_));
186
187 let provider_rw = provider_factory.provider_rw().unwrap();
189 provider_rw.remove_blocks_above(0).unwrap();
190 provider_rw.commit().unwrap();
191
192 let Err(ProviderError::ConsistentView(boxed_consistent_view_err)) = view.provider_ro()
194 else {
195 panic!("expected reorged consistent view error, got success");
196 };
197 let unboxed = *boxed_consistent_view_err;
198 assert_eq!(unboxed, ConsistentViewError::Reorged { block: initial_tip_hash });
199
200 let mut block = Block::default();
202 block.header_mut().parent_hash = genesis_hash;
203 block.header_mut().number = 1;
204 block.header_mut().extra_data =
205 Bytes::from_str("6a6f75726e657920746f20697468616361").unwrap();
206 let sealed_block = SealedBlock::seal_slow(block);
207 let recovered_block = RecoveredBlock::new_sealed(sealed_block, vec![]);
208
209 let provider_rw = provider_factory.provider_rw().unwrap();
211 provider_rw.insert_block(recovered_block).unwrap();
212 provider_rw.commit().unwrap();
213
214 let Err(ProviderError::ConsistentView(boxed_consistent_view_err)) = view.provider_ro()
216 else {
217 panic!("expected reorged consistent view error, got success");
218 };
219 let unboxed = *boxed_consistent_view_err;
220 assert_eq!(unboxed, ConsistentViewError::Reorged { block: initial_tip_hash });
221 }
222}