Skip to main content

tempo/
regenesis.rs

1//! Patch a virgin block-0 database to use a new genesis header.
2//!
3//! It replaces the header static file segment and rewrites the hash-to-number index
4//! without touching state, hash, or trie tables.
5
6use std::fs;
7
8use clap::Parser;
9use eyre::{ensure, eyre};
10use reth_chainspec::EthChainSpec;
11use reth_cli_commands::common::{CliNodeTypes, EnvironmentArgs};
12use reth_db::{DatabaseEnv, open_db};
13use reth_db_api::{
14    cursor::DbCursorRO,
15    tables,
16    transaction::{DbTx, DbTxMut},
17};
18use reth_ethereum::tasks::Runtime;
19use reth_node_builder::NodeTypesWithDBAdapter;
20use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives};
21use reth_provider::{
22    BlockNumReader, DatabaseProviderFactory, ProviderFactory, StaticFileProviderBuilder,
23    StaticFileProviderFactory, StaticFileSegment, StaticFileWriter, providers::RocksDBProvider,
24};
25use reth_storage_api::DBProvider;
26use tempo_chainspec::spec::TempoChainSpecParser;
27use tracing::info;
28
29/// Patch a block-0 database to use a new genesis header.
30#[derive(Debug, Parser)]
31pub struct Regenesis<C: reth_cli::chainspec::ChainSpecParser = TempoChainSpecParser> {
32    #[command(flatten)]
33    env: EnvironmentArgs<C>,
34}
35
36impl<C> Regenesis<C>
37where
38    C: reth_cli::chainspec::ChainSpecParser,
39    C::ChainSpec: EthChainSpec,
40{
41    pub(crate) async fn execute<N>(self, runtime: Runtime) -> eyre::Result<()>
42    where
43        N: CliNodeTypes<ChainSpec = C::ChainSpec>,
44        C::ChainSpec: EthChainSpec<Header = <N::Primitives as NodePrimitives>::BlockHeader>,
45    {
46        let new_genesis_hash = self.env.chain.genesis_hash();
47        let genesis_header = self.env.chain.genesis_header();
48        let genesis_block_number = genesis_header.number();
49        ensure!(
50            genesis_block_number == 0,
51            "regenesis only supports block-0 genesis headers, found genesis block {genesis_block_number}"
52        );
53
54        let data_dir = self
55            .env
56            .datadir
57            .clone()
58            .resolve_datadir(self.env.chain.chain());
59        fs::create_dir_all(data_dir.static_files())?;
60        fs::create_dir_all(data_dir.rocksdb())?;
61
62        let db = open_db(data_dir.db(), self.env.db.database_args())?;
63        let static_file_provider = StaticFileProviderBuilder::read_write(data_dir.static_files())
64            .with_metrics()
65            .with_genesis_block_number(genesis_block_number)
66            .build()?;
67        let rocksdb_provider = RocksDBProvider::builder(data_dir.rocksdb())
68            .with_default_tables()
69            .with_database_log_level(self.env.db.log_level)
70            .build()?;
71
72        let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<N, DatabaseEnv>>::new(
73            db,
74            self.env.chain.clone(),
75            static_file_provider,
76            rocksdb_provider,
77            runtime,
78        )?;
79        let provider_rw = provider_factory.database_provider_rw()?;
80
81        let last_block = provider_rw.last_block_number()?;
82        ensure!(
83            last_block == 0,
84            "regenesis only supports virgin block-0 databases, found block {last_block}"
85        );
86
87        let tx = provider_rw.tx_ref();
88        let (stored_genesis_hash, stored_block_number) = {
89            let mut cursor = tx.cursor_read::<tables::HeaderNumbers>()?;
90            let entry = cursor.first()?.ok_or_else(|| {
91                eyre!("regenesis requires exactly one HeaderNumbers entry, found none")
92            })?;
93            ensure!(
94                cursor.next()?.is_none(),
95                "regenesis requires exactly one HeaderNumbers entry, found more than one"
96            );
97            entry
98        };
99        ensure!(
100            stored_block_number == 0,
101            "only HeaderNumbers entry maps to block {stored_block_number}, expected block 0"
102        );
103
104        if stored_genesis_hash == new_genesis_hash {
105            info!(
106                target: "tempo::cli",
107                old_genesis_hash = %stored_genesis_hash,
108                %new_genesis_hash,
109                "Genesis hash already matches, skipping patch"
110            );
111            return Ok(());
112        }
113
114        let static_file_provider = provider_rw.static_file_provider();
115        static_file_provider.delete_segment(StaticFileSegment::Headers)?;
116        {
117            let mut writer = static_file_provider
118                .get_writer(genesis_block_number, StaticFileSegment::Headers)?;
119            writer.append_header(genesis_header, &new_genesis_hash)?;
120        }
121
122        tx.delete::<tables::HeaderNumbers>(stored_genesis_hash, None)?;
123        tx.put::<tables::HeaderNumbers>(new_genesis_hash, 0)?;
124        tx.put::<tables::BlockBodyIndices>(0, Default::default())?;
125        provider_rw.commit()?;
126
127        info!(
128            target: "tempo::cli",
129            old_genesis_hash = %stored_genesis_hash,
130            %new_genesis_hash,
131            "Patched genesis header index"
132        );
133
134        Ok(())
135    }
136}