reth_cli_commands/init_state/
without_evm.rs

1use 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
16/// Reads the header RLP from a file and returns the Header.
17///
18/// This supports both raw rlp bytes and rlp hex string.
19pub(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        // If UTF-8 decoding fails, read as raw bytes
27        reth_fs_util::read(path)?
28    };
29
30    let header = H::decode(&mut &buf[..])?;
31    Ok(header)
32}
33
34/// Creates a dummy chain (with no transactions) up to the last EVM block and appends the
35/// first valid block.
36pub 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    // Write EVM dummy data up to `header - 1` block
54    append_dummy_chain(&static_file_provider, header.number() - 1, header_factory)?;
55
56    info!(target: "reth::cli", "Appending first valid block.");
57
58    append_first_block(provider_rw, &header)?;
59
60    for stage in StageId::ALL {
61        provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(header.number()))?;
62    }
63
64    info!(target: "reth::cli", "Set up finished.");
65
66    Ok(())
67}
68
69/// Appends the first block.
70///
71/// By appending it, static file writer also verifies that all segments are at the same
72/// height.
73fn append_first_block<Provider>(
74    provider_rw: &Provider,
75    header: &SealedHeaderFor<Provider::Primitives>,
76) -> ProviderResult<()>
77where
78    Provider: BlockWriter<Block = <Provider::Primitives as NodePrimitives>::Block>
79        + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>,
80{
81    provider_rw.insert_block(
82        SealedBlock::<<Provider::Primitives as NodePrimitives>::Block>::from_sealed_parts(
83            header.clone(),
84            Default::default(),
85        )
86        .try_recover()
87        .expect("no senders or txes"),
88    )?;
89
90    let sf_provider = provider_rw.static_file_provider();
91
92    sf_provider.latest_writer(StaticFileSegment::Receipts)?.increment_block(header.number())?;
93
94    Ok(())
95}
96
97/// Creates a dummy chain with no transactions/receipts up to `target_height` block inclusive.
98///
99/// * Headers: It will push an empty block.
100/// * Transactions: It will not push any tx, only increments the end block range.
101/// * Receipts: It will not push any receipt, only increments the end block range.
102fn append_dummy_chain<N, F>(
103    sf_provider: &StaticFileProvider<N>,
104    target_height: BlockNumber,
105    header_factory: F,
106) -> ProviderResult<()>
107where
108    N: NodePrimitives,
109    F: Fn(BlockNumber) -> N::BlockHeader + Send + Sync + 'static,
110{
111    let (tx, rx) = std::sync::mpsc::channel();
112
113    // Spawn jobs for incrementing the block end range of transactions and receipts
114    for segment in [StaticFileSegment::Transactions, StaticFileSegment::Receipts] {
115        let tx_clone = tx.clone();
116        let provider = sf_provider.clone();
117        std::thread::spawn(move || {
118            let result = provider.latest_writer(segment).and_then(|mut writer| {
119                for block_num in 1..=target_height {
120                    writer.increment_block(block_num)?;
121                }
122                Ok(())
123            });
124
125            tx_clone.send(result).unwrap();
126        });
127    }
128
129    // Spawn job for appending empty headers
130    let provider = sf_provider.clone();
131    std::thread::spawn(move || {
132        let result = provider.latest_writer(StaticFileSegment::Headers).and_then(|mut writer| {
133            for block_num in 1..=target_height {
134                // TODO: should we fill with real parent_hash?
135                let header = header_factory(block_num);
136                writer.append_header(&header, &B256::ZERO)?;
137            }
138            Ok(())
139        });
140
141        tx.send(result).unwrap();
142    });
143
144    // Catches any StaticFileWriter error.
145    while let Ok(append_result) = rx.recv() {
146        if let Err(err) = append_result {
147            tracing::error!(target: "reth::cli", "Error appending dummy chain: {err}");
148            return Err(err)
149        }
150    }
151
152    // If, for any reason, rayon crashes this verifies if all segments are at the same
153    // target_height.
154    for segment in
155        [StaticFileSegment::Headers, StaticFileSegment::Receipts, StaticFileSegment::Transactions]
156    {
157        assert_eq!(
158            sf_provider.latest_writer(segment)?.user_header().block_end(),
159            Some(target_height),
160            "Static file segment {segment} was unsuccessful advancing its block height."
161        );
162    }
163
164    Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use alloy_consensus::Header;
171    use alloy_primitives::{address, b256};
172    use reth_db_common::init::init_genesis;
173    use reth_provider::{test_utils::create_test_provider_factory, DatabaseProviderFactory};
174    use std::io::Write;
175    use tempfile::NamedTempFile;
176
177    #[test]
178    fn test_read_header_from_file_hex_string() {
179        let header_rlp = "0xf90212a00d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479471562b71999873db5b286df957af199ec94617f7a0f496f3d199c51a1aaee67dac95f24d92ac13c60d25181e1eecd6eca5ddf32ac0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808206a4840365908a808468e975f09ad983011003846765746888676f312e32352e308664617277696ea06f485a167165ec12e0ab3e6ab59a7b88560b90306ac98a26eb294abf95a8c59b88000000000000000007";
180
181        let mut temp_file = NamedTempFile::new().unwrap();
182        temp_file.write_all(header_rlp.as_bytes()).unwrap();
183        temp_file.flush().unwrap();
184
185        let header: Header = read_header_from_file(temp_file.path()).unwrap();
186
187        assert_eq!(header.number, 1700);
188        assert_eq!(
189            header.parent_hash,
190            b256!("0d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dd")
191        );
192        assert_eq!(header.beneficiary, address!("71562b71999873db5b286df957af199ec94617f7"));
193    }
194
195    #[test]
196    fn test_read_header_from_file_raw_bytes() {
197        let header_rlp = "0xf90212a00d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479471562b71999873db5b286df957af199ec94617f7a0f496f3d199c51a1aaee67dac95f24d92ac13c60d25181e1eecd6eca5ddf32ac0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808206a4840365908a808468e975f09ad983011003846765746888676f312e32352e308664617277696ea06f485a167165ec12e0ab3e6ab59a7b88560b90306ac98a26eb294abf95a8c59b88000000000000000007";
198        let header_bytes =
199            alloy_primitives::hex::decode(header_rlp.trim_start_matches("0x")).unwrap();
200
201        let mut temp_file = NamedTempFile::new().unwrap();
202        temp_file.write_all(&header_bytes).unwrap();
203        temp_file.flush().unwrap();
204
205        let header: Header = read_header_from_file(temp_file.path()).unwrap();
206
207        assert_eq!(header.number, 1700);
208        assert_eq!(
209            header.parent_hash,
210            b256!("0d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dd")
211        );
212        assert_eq!(header.beneficiary, address!("71562b71999873db5b286df957af199ec94617f7"));
213    }
214
215    #[test]
216    fn test_setup_without_evm_succeeds() {
217        let header_rlp = "0xf90212a00d84d79f59fc384a1f6402609a5b7253b4bfe7a4ae12608ed107273e5422b6dda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479471562b71999873db5b286df957af199ec94617f7a0f496f3d199c51a1aaee67dac95f24d92ac13c60d25181e1eecd6eca5ddf32ac0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808206a4840365908a808468e975f09ad983011003846765746888676f312e32352e308664617277696ea06f485a167165ec12e0ab3e6ab59a7b88560b90306ac98a26eb294abf95a8c59b88000000000000000007";
218        let header_bytes =
219            alloy_primitives::hex::decode(header_rlp.trim_start_matches("0x")).unwrap();
220
221        let mut temp_file = NamedTempFile::new().unwrap();
222        temp_file.write_all(&header_bytes).unwrap();
223        temp_file.flush().unwrap();
224
225        let header: Header = read_header_from_file(temp_file.path()).unwrap();
226        let header_hash = b256!("4f05e4392969fc82e41f6d6a8cea379323b0b2d3ddf7def1a33eec03883e3a33");
227
228        let provider_factory = create_test_provider_factory();
229
230        init_genesis(&provider_factory).unwrap();
231
232        let provider_rw = provider_factory.database_provider_rw().unwrap();
233
234        setup_without_evm(&provider_rw, SealedHeader::new(header, header_hash), |number| Header {
235            number,
236            ..Default::default()
237        })
238        .unwrap();
239
240        let static_files = provider_factory.static_file_provider();
241        let writer = static_files.latest_writer(StaticFileSegment::Headers).unwrap();
242        let actual_next_height = writer.next_block_number();
243        let expected_next_height = 1701;
244
245        assert_eq!(actual_next_height, expected_next_height);
246    }
247}