reth_cli_commands/init_state/
without_evm.rs1use alloy_consensus::BlockHeader;
2use alloy_primitives::{BlockNumber, B256};
3use alloy_rlp::Decodable;
4use eyre::WrapErr;
5use reth_codecs::Compact;
6use reth_fs_util::FsPathError;
7use reth_node_builder::NodePrimitives;
8use reth_primitives_traits::{SealedBlock, SealedHeader, SealedHeaderFor};
9use reth_provider::{
10 providers::StaticFileProvider, BlockWriter, ProviderError, ProviderResult,
11 StageCheckpointWriter, StaticFileProviderFactory, StaticFileWriter,
12};
13use reth_stages::{StageCheckpoint, StageId};
14use reth_static_file_types::StaticFileSegment;
15use std::{io::ErrorKind, path::Path};
16use tracing::info;
17
18pub(crate) fn read_header_from_file<H>(path: &Path) -> Result<H, eyre::Error>
22where
23 H: Decodable,
24{
25 let buf = match reth_fs_util::read_to_string(path) {
26 Ok(content) => alloy_primitives::hex::decode(content.trim())
27 .wrap_err_with(|| format!("failed to hex-decode header file {}", path.display()))?,
28 Err(FsPathError::Read { source, .. }) if source.kind() == ErrorKind::InvalidData => {
30 reth_fs_util::read(path)?
31 }
32 Err(err) => return Err(err.into()),
33 };
34
35 H::decode(&mut &buf[..])
36 .wrap_err_with(|| format!("failed to RLP-decode header file {}", path.display()))
37}
38
39pub fn setup_without_evm<Provider, F>(
42 provider_rw: &Provider,
43 header: SealedHeader<<Provider::Primitives as NodePrimitives>::BlockHeader>,
44 header_factory: F,
45) -> ProviderResult<()>
46where
47 Provider: StaticFileProviderFactory
48 + StageCheckpointWriter
49 + BlockWriter<Block = <Provider::Primitives as NodePrimitives>::Block>,
50 F: Fn(BlockNumber) -> <Provider::Primitives as NodePrimitives>::BlockHeader
51 + Send
52 + Sync
53 + 'static,
54{
55 info!(target: "reth::cli", new_tip = ?header.num_hash(), "Setting up dummy EVM chain before importing state.");
56
57 let static_file_provider = provider_rw.static_file_provider();
58 if header.number() > 0 {
63 append_dummy_chain(&static_file_provider, header.number() - 1, header_factory)?;
64 }
65
66 info!(target: "reth::cli", "Appending first valid block.");
67
68 append_first_block(provider_rw, &header)?;
69
70 for stage in StageId::ALL {
71 provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(header.number()))?;
72 }
73
74 info!(target: "reth::cli", "Set up finished.");
75
76 Ok(())
77}
78
79fn append_first_block<Provider>(
84 provider_rw: &Provider,
85 header: &SealedHeaderFor<Provider::Primitives>,
86) -> ProviderResult<()>
87where
88 Provider: BlockWriter<Block = <Provider::Primitives as NodePrimitives>::Block>
89 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>,
90{
91 provider_rw.insert_block(
92 &SealedBlock::<<Provider::Primitives as NodePrimitives>::Block>::from_sealed_parts(
93 header.clone(),
94 Default::default(),
95 )
96 .try_recover()
97 .expect("no senders or txes"),
98 )?;
99
100 let sf_provider = provider_rw.static_file_provider();
101
102 sf_provider.latest_writer(StaticFileSegment::Receipts)?.increment_block(header.number())?;
103
104 Ok(())
105}
106
107fn append_dummy_chain<N, F>(
114 sf_provider: &StaticFileProvider<N>,
115 target_height: BlockNumber,
116 header_factory: F,
117) -> ProviderResult<()>
118where
119 N: NodePrimitives,
120 F: Fn(BlockNumber) -> N::BlockHeader + Send + Sync + 'static,
121{
122 let (tx, rx) = std::sync::mpsc::channel();
123
124 let mut handles = Vec::new();
125
126 for segment in [
128 StaticFileSegment::Transactions,
129 StaticFileSegment::Receipts,
130 StaticFileSegment::TransactionSenders,
131 ] {
132 if sf_provider.get_highest_static_file_block(segment).is_none() {
133 continue
134 }
135 let tx_clone = tx.clone();
136 let provider = sf_provider.clone();
137 let thread_name = match segment {
138 StaticFileSegment::Transactions => "init-state-txs",
139 StaticFileSegment::Receipts => "init-state-receipts",
140 StaticFileSegment::TransactionSenders => "init-state-senders",
141 _ => "init-state-segment",
142 };
143 let handle = reth_tasks::spawn_os_thread(thread_name, move || {
144 let result = provider.latest_writer(segment).and_then(|mut writer| {
145 for block_num in 1..=target_height {
146 writer.increment_block(block_num)?;
147 }
148 Ok(())
149 });
150
151 let _ = tx_clone.send(result);
153 });
154 handles.push((thread_name, handle));
155 }
156
157 let provider = sf_provider.clone();
159 let handle = reth_tasks::spawn_os_thread("init-state-headers", move || {
160 let result = provider.latest_writer(StaticFileSegment::Headers).and_then(|mut writer| {
161 for block_num in 1..=target_height {
162 let header = header_factory(block_num);
164 writer.append_header(&header, &B256::ZERO)?;
165 }
166 Ok(())
167 });
168
169 let _ = tx.send(result);
170 });
171 handles.push(("init-state-headers", handle));
172
173 while let Ok(append_result) = rx.recv() {
175 if let Err(err) = append_result {
176 tracing::error!(target: "reth::cli", "Error appending dummy chain: {err}");
177 return Err(err)
178 }
179 }
180
181 for (name, handle) in handles {
183 if handle.join().is_err() {
184 let err = std::io::Error::other(format!("init-state worker {name} panicked"));
185 tracing::error!(target: "reth::cli", "Error appending dummy chain: {err}");
186 return Err(ProviderError::other(err))
187 }
188 }
189
190 for segment in [
193 StaticFileSegment::Headers,
194 StaticFileSegment::Receipts,
195 StaticFileSegment::Transactions,
196 StaticFileSegment::TransactionSenders,
197 ] {
198 if sf_provider.get_highest_static_file_block(segment).is_none() {
199 continue
200 }
201 assert_eq!(
202 sf_provider.latest_writer(segment)?.user_header().block_end(),
203 Some(target_height),
204 "Static file segment {segment} was unsuccessful advancing its block height."
205 );
206 }
207
208 Ok(())
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use alloy_consensus::Header;
215 use alloy_primitives::{address, b256};
216 use reth_db_common::init::init_genesis;
217 use reth_provider::{test_utils::create_test_provider_factory, DatabaseProviderFactory};
218 use std::{
219 io::Write,
220 sync::{
221 atomic::{AtomicU64, Ordering},
222 Arc,
223 },
224 };
225 use tempfile::NamedTempFile;
226
227 #[test]
228 fn test_read_header_from_file_hex_string() {
229 let header_rlp = "0xf90212a00d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479471562b71999873db5b286df957af199ec94617f7a0f496f3d199c51a1aaee67dac95f24d92ac13c60d25181e1eecd6eca5ddf32ac0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808206a4840365908a808468e975f09ad983011003846765746888676f312e32352e308664617277696ea06f485a167165ec12e0ab3e6ab59a7b88560b90306ac98a26eb294abf95a8c59b88000000000000000007";
230
231 let mut temp_file = NamedTempFile::new().unwrap();
232 temp_file.write_all(header_rlp.as_bytes()).unwrap();
233 temp_file.flush().unwrap();
234
235 let header: Header = read_header_from_file(temp_file.path()).unwrap();
236
237 assert_eq!(header.number, 1700);
238 assert_eq!(
239 header.parent_hash,
240 b256!("0d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dd")
241 );
242 assert_eq!(header.beneficiary, address!("71562b71999873db5b286df957af199ec94617f7"));
243 }
244
245 #[test]
246 fn test_read_header_from_file_raw_bytes() {
247 let header_rlp = "0xf90212a00d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479471562b71999873db5b286df957af199ec94617f7a0f496f3d199c51a1aaee67dac95f24d92ac13c60d25181e1eecd6eca5ddf32ac0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808206a4840365908a808468e975f09ad983011003846765746888676f312e32352e308664617277696ea06f485a167165ec12e0ab3e6ab59a7b88560b90306ac98a26eb294abf95a8c59b88000000000000000007";
248 let header_bytes =
249 alloy_primitives::hex::decode(header_rlp.trim_start_matches("0x")).unwrap();
250
251 let mut temp_file = NamedTempFile::new().unwrap();
252 temp_file.write_all(&header_bytes).unwrap();
253 temp_file.flush().unwrap();
254
255 let header: Header = read_header_from_file(temp_file.path()).unwrap();
256
257 assert_eq!(header.number, 1700);
258 assert_eq!(
259 header.parent_hash,
260 b256!("0d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dd")
261 );
262 assert_eq!(header.beneficiary, address!("71562b71999873db5b286df957af199ec94617f7"));
263 }
264
265 #[test]
266 fn test_setup_without_evm_succeeds() {
267 let header_rlp = "0xf90212a00d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479471562b71999873db5b286df957af199ec94617f7a0f496f3d199c51a1aaee67dac95f24d92ac13c60d25181e1eecd6eca5ddf32ac0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808206a4840365908a808468e975f09ad983011003846765746888676f312e32352e308664617277696ea06f485a167165ec12e0ab3e6ab59a7b88560b90306ac98a26eb294abf95a8c59b88000000000000000007";
268 let header_bytes =
269 alloy_primitives::hex::decode(header_rlp.trim_start_matches("0x")).unwrap();
270
271 let mut temp_file = NamedTempFile::new().unwrap();
272 temp_file.write_all(&header_bytes).unwrap();
273 temp_file.flush().unwrap();
274
275 let header: Header = read_header_from_file(temp_file.path()).unwrap();
276 let header_hash = b256!("4f05e4392969fc82e41f6d6a8cea379323b0b2d3ddf7def1a33eec03883e3a33");
277
278 let provider_factory = create_test_provider_factory();
279
280 init_genesis(&provider_factory).unwrap();
281
282 let provider_rw = provider_factory.database_provider_rw().unwrap();
283
284 setup_without_evm(&provider_rw, SealedHeader::new(header, header_hash), |number| Header {
285 number,
286 ..Default::default()
287 })
288 .unwrap();
289
290 let static_files = provider_factory.static_file_provider();
291 let writer = static_files.latest_writer(StaticFileSegment::Headers).unwrap();
292 let actual_next_height = writer.next_block_number();
293 let expected_next_height = 1701;
294
295 assert_eq!(actual_next_height, expected_next_height);
296 }
297
298 #[test]
304 fn test_setup_without_evm_skips_dummy_chain_for_genesis_header() {
305 let header = Header { number: 0, ..Default::default() };
306 let header_hash = header.hash_slow();
307
308 let provider_factory = create_test_provider_factory();
309 init_genesis(&provider_factory).unwrap();
310 let provider_rw = provider_factory.database_provider_rw().unwrap();
311
312 let factory_calls = Arc::new(AtomicU64::new(0));
313 let factory_calls_inner = Arc::clone(&factory_calls);
314
315 let _ = setup_without_evm(
321 &provider_rw,
322 SealedHeader::new(header, header_hash),
323 move |number| {
324 let n = factory_calls_inner.fetch_add(1, Ordering::Relaxed);
327 assert!(n < 8, "header_factory must not be invoked for a genesis-block header");
328 Header { number, ..Default::default() }
329 },
330 );
331
332 assert_eq!(
333 factory_calls.load(Ordering::Relaxed),
334 0,
335 "append_dummy_chain must be skipped when header.number() == 0"
336 );
337 }
338}