Skip to main content

reth_cli_commands/init_state/
without_evm.rs

1use 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
18/// Reads the header RLP from a file and returns the Header.
19///
20/// This supports both raw rlp bytes and rlp hex string.
21pub(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        // Non-UTF-8 content is raw RLP bytes.
29        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
39/// Creates a dummy chain (with no transactions) up to the last EVM block and appends the
40/// first valid block.
41pub 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    // Write EVM dummy data up to `header - 1` block. Skip when the supplied
59    // header is at block 0: `header.number() - 1` would underflow in u64 to
60    // `u64::MAX`, sending `append_dummy_chain` into a 1..=u64::MAX loop that
61    // exhausts memory before failing.
62    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
79/// Appends the first block.
80///
81/// By appending it, static file writer also verifies that all segments are at the same
82/// height.
83fn 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
107/// Creates a dummy chain with no transactions/receipts up to `target_height` block inclusive.
108///
109/// * Headers: It will push an empty block.
110/// * Transactions: It will not push any tx, only increments the end block range.
111/// * Receipts: It will not push any receipt, only increments the end block range.
112/// * TransactionSenders: If the segment exists, increments the end block range.
113fn 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    // Spawn jobs for incrementing the block end range of transactions, receipts, and senders.
127    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            // The receiver is gone if it already returned an earlier error.
152            let _ = tx_clone.send(result);
153        });
154        handles.push((thread_name, handle));
155    }
156
157    // Spawn job for appending empty headers
158    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                // TODO: should we fill with real parent_hash?
163                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    // Catches any StaticFileWriter error.
174    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    // A panicked worker drops its sender without reporting a result.
182    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    // If, for any reason, rayon crashes this verifies if all segments are at the same
191    // target_height.
192    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    /// Regression: a header at block 0 used to send `append_dummy_chain` into
299    /// a `1..=u64::MAX` loop because `header.number() - 1` underflowed in
300    /// u64. The guard `if header.number() > 0` skips the dummy-chain step
301    /// when there is no pre-genesis range to backfill, so `header_factory`
302    /// is never invoked.
303    #[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        // The Result of `setup_without_evm` itself is not asserted: with
316        // `number == 0` plus a genesis already written by `init_genesis`,
317        // the subsequent `append_first_block` may legitimately fail. The
318        // bug under test is the OOM in the dummy-chain loop, observable
319        // through the factory-call counter below.
320        let _ = setup_without_evm(
321            &provider_rw,
322            SealedHeader::new(header, header_hash),
323            move |number| {
324                // Bound calls so a regression cannot exhaust the test
325                // runner's memory; the only correct value here is 0.
326                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}