1use 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#[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}