1#![allow(dead_code)]
3use anyhow;
4use cnidarium::{Snapshot, StateDelta, StateRead, StateWrite, Storage};
5use futures::StreamExt as _;
6use jmt::RootHash;
7use penumbra_sdk_app::app::StateReadExt as _;
8use penumbra_sdk_app::SUBSTORE_PREFIXES;
9use penumbra_sdk_proto::core::component::sct::v1::query_service_server::QueryService;
10use penumbra_sdk_proto::penumbra::core::component as pb;
11use penumbra_sdk_proto::StateWriteProto;
12use penumbra_sdk_sct::component::clock::{EpochManager, EpochRead};
13use penumbra_sdk_sct::component::rpc::Server as SctServer;
14use penumbra_sdk_tct::Position;
15use prost::Message;
16use std::path::PathBuf;
17use std::sync::Arc;
18use tonic::IntoRequest;
19
20use crate::network::generate::NetworkConfig;
21
22#[derive(Clone)]
24struct Context {
25 sct_server: Arc<SctServer>,
26}
27
28impl Context {
29 fn new(storage: Storage) -> Self {
31 Self {
32 sct_server: Arc::new(SctServer::new(storage)),
33 }
34 }
35
36 async fn epoch_height_to_index(&self, epoch_starting_height: u64) -> anyhow::Result<u64> {
38 Ok(self
39 .sct_server
40 .epoch_by_height(
41 pb::sct::v1::EpochByHeightRequest {
42 height: epoch_starting_height,
43 }
44 .into_request(),
45 )
46 .await?
47 .into_inner()
48 .epoch
49 .expect(&format!(
50 "epoch at height {} should be present",
51 epoch_starting_height
52 ))
53 .index)
54 }
55
56 #[allow(deprecated)]
59 async fn translate_bsod(
60 &self,
61 bsod: pb::dex::v1::BatchSwapOutputData,
62 ) -> anyhow::Result<pb::dex::v1::BatchSwapOutputData> {
63 let sct_position_prefix: u64 = {
64 let epoch = self
65 .epoch_height_to_index(bsod.epoch_starting_height)
66 .await?;
67 Position::from((
68 u16::try_from(epoch).expect("epoch should fit in 16 bits"),
69 u16::try_from(bsod.height - bsod.epoch_starting_height)
70 .expect("block index should fit in 16 bits"),
71 0,
72 ))
73 .into()
74 };
75 Ok(pb::dex::v1::BatchSwapOutputData {
76 sct_position_prefix,
77 epoch_starting_height: Default::default(),
78 ..bsod
79 })
80 }
81
82 async fn translate_compact_block(
83 &self,
84 compact_block: pb::compact_block::v1::CompactBlock,
85 ) -> anyhow::Result<pb::compact_block::v1::CompactBlock> {
86 let mut swap_outputs = Vec::with_capacity(compact_block.swap_outputs.len());
87 for bsod in compact_block.swap_outputs {
88 swap_outputs.push(self.translate_bsod(bsod).await?);
89 }
90 Ok(pb::compact_block::v1::CompactBlock {
91 swap_outputs,
92 ..compact_block
93 })
94 }
95}
96
97async fn translate_dex_storage(
99 ctx: Context,
100 delta: &mut StateDelta<Snapshot>,
101) -> anyhow::Result<()> {
102 let mut stream = delta.prefix_raw("dex/output/");
103 while let Some(r) = stream.next().await {
104 let (key, bsod_bytes) = r?;
105 let bsod = pb::dex::v1::BatchSwapOutputData::decode(bsod_bytes.as_slice())?;
106 let bsod = ctx.translate_bsod(bsod).await?;
107 delta.put_proto(key, bsod);
108 }
109 Ok(())
110}
111
112async fn translate_compact_block_storage(
114 ctx: Context,
115 delta: &mut StateDelta<Snapshot>,
116) -> anyhow::Result<()> {
117 let mut stream = delta.nonverifiable_prefix_raw("compactblock/".as_bytes());
118 while let Some(r) = stream.next().await {
119 let (key, compactblock_bytes) = r?;
120 let block = pb::compact_block::v1::CompactBlock::decode(compactblock_bytes.as_slice())?;
121 let block = ctx.translate_compact_block(block).await?;
122 delta.nonverifiable_put_raw(key, block.encode_to_vec());
123 }
124 Ok(())
125}
126
127pub async fn migrate(
129 storage: Storage,
130 path_to_export: PathBuf,
131 genesis_start: Option<tendermint::time::Time>,
132) -> anyhow::Result<()> {
133 let export_state = storage.latest_snapshot();
134 let root_hash = export_state.root_hash().await.expect("can get root hash");
135 let pre_upgrade_root_hash: RootHash = root_hash.into();
136 let pre_upgrade_height = export_state
137 .get_block_height()
138 .await
139 .expect("can get block height");
140 let post_upgrade_height = pre_upgrade_height.wrapping_add(1);
141
142 let mut delta = StateDelta::new(export_state);
143 let (migration_duration, post_upgrade_root_hash) = {
144 let start_time = std::time::SystemTime::now();
145 let ctx = Context::new(storage.clone());
146
147 translate_dex_storage(ctx.clone(), &mut delta).await?;
149 translate_compact_block_storage(ctx.clone(), &mut delta).await?;
151
152 delta.put_block_height(0u64);
153 let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
154 tracing::info!(?post_upgrade_root_hash, "post-upgrade root hash");
155
156 (
157 start_time.elapsed().expect("start time not set"),
158 post_upgrade_root_hash,
159 )
160 };
161
162 storage.release().await;
163
164 let rocksdb_dir = path_to_export.join("rocksdb");
165 let storage = Storage::load(rocksdb_dir, SUBSTORE_PREFIXES.to_vec()).await?;
166 let migrated_state = storage.latest_snapshot();
167
168 let chain_id = migrated_state.get_chain_id().await?;
172 let app_state = penumbra_sdk_app::genesis::Content {
173 chain_id,
174 ..Default::default()
175 };
176 let mut genesis = NetworkConfig::make_genesis(app_state.clone()).expect("can make genesis");
177 genesis.app_hash = post_upgrade_root_hash
178 .0
179 .to_vec()
180 .try_into()
181 .expect("infaillible conversion");
182 genesis.initial_height = post_upgrade_height as i64;
183 genesis.genesis_time = genesis_start.unwrap_or_else(|| {
184 let now = tendermint::time::Time::now();
185 tracing::info!(%now, "no genesis time provided, detecting a testing setup");
186 now
187 });
188 let checkpoint = post_upgrade_root_hash.0.to_vec();
189 let genesis = NetworkConfig::make_checkpoint(genesis, Some(checkpoint));
190
191 let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
192 tracing::info!("genesis: {}", genesis_json);
193 let genesis_path = path_to_export.join("genesis.json");
194 std::fs::write(genesis_path, genesis_json).expect("can write genesis");
195
196 let validator_state_path = path_to_export.join("priv_validator_state.json");
197 let fresh_validator_state = crate::network::generate::NetworkValidator::initial_state();
198 std::fs::write(validator_state_path, fresh_validator_state).expect("can write validator state");
199
200 tracing::info!(
201 pre_upgrade_height,
202 post_upgrade_height,
203 ?pre_upgrade_root_hash,
204 ?post_upgrade_root_hash,
205 duration = migration_duration.as_secs(),
206 "successful migration!"
207 );
208
209 Ok(())
210}