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