1#![allow(dead_code)]
3use anyhow::Context;
4use cnidarium::StateRead;
5use cnidarium::{Snapshot, StateDelta, StateWrite, Storage};
6use futures::TryStreamExt as _;
7use futures::{pin_mut, StreamExt};
8use jmt::RootHash;
9use penumbra_sdk_app::app::StateReadExt as _;
10use penumbra_sdk_dex::component::{PositionManager, StateReadExt, StateWriteExt};
11use penumbra_sdk_dex::lp::position;
12use penumbra_sdk_dex::lp::position::Position;
13use penumbra_sdk_governance::proposal_state::State as ProposalState;
14use penumbra_sdk_governance::Proposal;
15use penumbra_sdk_governance::StateReadExt as _;
16use penumbra_sdk_governance::StateWriteExt as _;
17use penumbra_sdk_proto::core::component::governance::v1 as pb_governance;
18use penumbra_sdk_proto::{StateReadProto, StateWriteProto};
19use penumbra_sdk_sct::component::clock::EpochManager;
20use penumbra_sdk_sct::component::clock::EpochRead;
21use penumbra_sdk_stake::validator::Validator;
22use std::fs::OpenOptions;
23use std::io::Write;
24use std::path::PathBuf;
25use tendermint_config::TendermintConfig;
26use tracing::instrument;
27
28use crate::network::generate::NetworkConfig;
29
30#[instrument]
56pub async fn migrate(
57 storage: Storage,
58 pd_home: PathBuf,
59 genesis_start: Option<tendermint::time::Time>,
60) -> anyhow::Result<()> {
61 let initial_state = storage.latest_snapshot();
63
64 let chain_id = initial_state.get_chain_id().await?;
65 let root_hash = initial_state
66 .root_hash()
67 .await
68 .expect("chain state has a root hash");
69
70 let pre_upgrade_height = initial_state
71 .get_block_height()
72 .await
73 .expect("chain state has a block height");
74 let post_upgrade_height = pre_upgrade_height.wrapping_add(1);
75
76 let pre_upgrade_root_hash: RootHash = root_hash.into();
77
78 let mut delta = StateDelta::new(initial_state);
80
81 let (migration_duration, post_upgrade_root_hash) = {
82 let start_time = std::time::SystemTime::now();
83
84 delete_empty_deleted_packet_commitments(&mut delta).await?;
86
87 truncate_validator_fields(&mut delta).await?;
89
90 truncate_proposal_fields(&mut delta).await?;
92
93 truncate_proposal_outcome_fields(&mut delta).await?;
95
96 update_dex_params(&mut delta).await?;
98
99 reindex_dex_positions(&mut delta).await?;
101
102 delta.ready_to_start();
104 delta.put_block_height(0u64);
105
106 let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
108 tracing::info!(?post_upgrade_root_hash, "post-migration root hash");
109
110 (
111 start_time.elapsed().expect("start is set"),
112 post_upgrade_root_hash,
113 )
114 };
115
116 tracing::info!("migration completed, generating genesis and signing state...");
117
118 let app_state = penumbra_sdk_app::genesis::Content {
120 chain_id,
121 ..Default::default()
122 };
123 let mut genesis = NetworkConfig::make_genesis(app_state.clone()).expect("can make genesis");
124 genesis.app_hash = post_upgrade_root_hash
125 .0
126 .to_vec()
127 .try_into()
128 .expect("infaillible conversion");
129
130 genesis.initial_height = post_upgrade_height as i64;
131 genesis.genesis_time = genesis_start.unwrap_or_else(|| {
132 let now = tendermint::time::Time::now();
133 tracing::info!(%now, "no genesis time provided, detecting a testing setup");
134 now
135 });
136
137 tracing::info!("generating checkpointed genesis");
138 let checkpoint = post_upgrade_root_hash.0.to_vec();
139 let genesis = NetworkConfig::make_checkpoint(genesis, Some(checkpoint));
140
141 tracing::info!("writing genesis to disk");
142 let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
143 tracing::info!("genesis: {}", genesis_json);
144 let genesis_path = pd_home.join("genesis.json");
145 std::fs::write(genesis_path, genesis_json).expect("can write genesis");
146
147 tracing::info!("updating signing state");
148 let validator_state_path = pd_home.join("priv_validator_state.json");
149 let fresh_validator_state = crate::network::generate::NetworkValidator::initial_state();
150 std::fs::write(validator_state_path, fresh_validator_state).expect("can write validator state");
151
152 tracing::info!(
153 pre_upgrade_height,
154 post_upgrade_height,
155 ?pre_upgrade_root_hash,
156 ?post_upgrade_root_hash,
157 duration = migration_duration.as_secs(),
158 "migration fully complete"
159 );
160
161 Ok(())
162}
163
164async fn delete_empty_deleted_packet_commitments(
165 delta: &mut StateDelta<Snapshot>,
166) -> anyhow::Result<()> {
167 let prefix_key = "ibc-data/commitments/";
168 let stream = delta.prefix_raw(&prefix_key);
169
170 pin_mut!(stream);
171
172 while let Some(entry) = stream.next().await {
173 let (key, value) = entry?;
174 if value.is_empty() {
175 delta.delete(key);
176 }
177 }
178
179 Ok(())
180}
181
182async fn update_dex_params(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
184 let mut dex_params = delta
185 .get_dex_params()
186 .await
187 .expect("chain state is initialized");
188 dex_params.max_execution_budget = 64;
189 delta.put_dex_params(dex_params);
190
191 Ok(())
192}
193
194async fn reindex_dex_positions(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
196 tracing::info!("running dex re-indexing migration");
197 let prefix_key_lp = penumbra_sdk_dex::state_key::all_positions();
198 let stream_all_lp = delta.prefix::<Position>(&prefix_key_lp);
199 let stream_open_lp = stream_all_lp.filter_map(|entry| async {
200 match entry {
201 Ok((_, lp)) if lp.state == position::State::Opened => Some(lp),
202 _ => None,
203 }
204 });
205 pin_mut!(stream_open_lp);
206
207 while let Some(lp) = stream_open_lp.next().await {
208 let id = lp.id();
210 delta.close_position_by_id(&id).await?;
212 delta.delete(penumbra_sdk_dex::state_key::position_by_id(&id));
214 delta.open_position(lp).await?;
216 }
217 tracing::info!("completed dex migration");
218 Ok(())
219}
220
221async fn truncate_validator_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
226 tracing::info!("truncating validator fields");
227 let key_prefix_validators = penumbra_sdk_stake::state_key::validators::definitions::prefix();
228 let all_validators = delta
229 .prefix_proto::<penumbra_sdk_proto::core::component::stake::v1::Validator>(
230 &key_prefix_validators,
231 )
232 .try_collect::<Vec<(
233 String,
234 penumbra_sdk_proto::core::component::stake::v1::Validator,
235 )>>()
236 .await?;
237
238 for (key, mut validator) in all_validators {
239 validator.name = truncate(&validator.name, 140).to_string();
240 validator.website = truncate(&validator.website, 70).to_string();
241 validator.description = truncate(&validator.description, 280).to_string();
242
243 let validator: Validator = validator.try_into()?;
245 tracing::info!("put key {:?}", key);
246 delta.put(key, validator);
247 }
248
249 Ok(())
250}
251
252async fn truncate_proposal_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
266 tracing::info!("truncating proposal fields");
267 let next_proposal_id: u64 = delta.next_proposal_id().await?;
268
269 for proposal_id in 0..next_proposal_id {
271 tracing::info!("truncating proposal: {}", proposal_id);
272 let proposal = delta
273 .get_proto::<pb_governance::Proposal>(
274 &penumbra_sdk_governance::state_key::proposal_definition(proposal_id),
275 )
276 .await?;
277
278 if proposal.is_none() {
279 break;
280 }
281
282 let mut proposal = proposal.unwrap();
283
284 proposal.title = truncate(&proposal.title, 80).to_string();
285 proposal.description = truncate(&proposal.description, 10_000).to_string();
286
287 match proposal
289 .payload
290 .clone()
291 .expect("proposal payload always set")
292 {
293 pb_governance::proposal::Payload::Signaling(commit) => {
294 proposal.payload = Some(pb_governance::proposal::Payload::Signaling(
295 pb_governance::proposal::Signaling {
296 commit: truncate(&commit.commit, 255).to_string(),
297 },
298 ));
299 }
300 pb_governance::proposal::Payload::Emergency(_halt_chain) => {}
301 pb_governance::proposal::Payload::ParameterChange(mut param_change) => {
302 for (i, mut change) in param_change.changes.clone().into_iter().enumerate() {
303 let key = truncate(&change.key, 64).to_string();
304 let value = truncate(&change.value, 2048).to_string();
305 let component = truncate(&change.component, 64).to_string();
306
307 change.key = key;
308 change.value = value;
309 change.component = component;
310
311 param_change.changes[i] = change;
312 }
313
314 for (i, mut change) in param_change.preconditions.clone().into_iter().enumerate() {
315 let key = truncate(&change.key, 64).to_string();
316 let value = truncate(&change.value, 2048).to_string();
317 let component = truncate(&change.component, 64).to_string();
318
319 change.key = key;
320 change.value = value;
321 change.component = component;
322
323 param_change.preconditions[i] = change;
324 }
325
326 proposal.payload = Some(pb_governance::proposal::Payload::ParameterChange(
327 param_change,
328 ));
329 }
330 pb_governance::proposal::Payload::CommunityPoolSpend(_transaction_plan) => {}
331 pb_governance::proposal::Payload::UpgradePlan(_height) => {}
332 pb_governance::proposal::Payload::FreezeIbcClient(client_id) => {
333 proposal.payload = Some(pb_governance::proposal::Payload::FreezeIbcClient(
334 pb_governance::proposal::FreezeIbcClient {
335 client_id: truncate(&client_id.client_id, 128).to_string(),
336 },
337 ));
338 }
339 pb_governance::proposal::Payload::UnfreezeIbcClient(client_id) => {
340 proposal.payload = Some(pb_governance::proposal::Payload::UnfreezeIbcClient(
341 pb_governance::proposal::UnfreezeIbcClient {
342 client_id: truncate(&client_id.client_id, 128).to_string(),
343 },
344 ));
345 }
346 };
347
348 tracing::info!(
350 "put key {:?}",
351 penumbra_sdk_governance::state_key::proposal_definition(proposal_id)
352 );
353 let proposal: Proposal = proposal.try_into()?;
355 delta.put(
356 penumbra_sdk_governance::state_key::proposal_definition(proposal_id),
357 proposal,
358 );
359 }
360
361 Ok(())
362}
363
364async fn truncate_proposal_outcome_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
367 tracing::info!("truncating proposal outcome fields");
368 let next_proposal_id: u64 = delta.next_proposal_id().await?;
369
370 for proposal_id in 0..next_proposal_id {
372 tracing::info!("truncating proposal outcomes: {}", proposal_id);
373 let proposal_state = delta
374 .get_proto::<pb_governance::ProposalState>(
375 &penumbra_sdk_governance::state_key::proposal_state(proposal_id),
376 )
377 .await?;
378
379 if proposal_state.is_none() {
380 break;
381 }
382
383 let mut proposal_state = proposal_state.unwrap();
384
385 match proposal_state
386 .state
387 .clone()
388 .expect("proposal state always set")
389 {
390 pb_governance::proposal_state::State::Withdrawn(reason) => {
391 proposal_state.state = Some(pb_governance::proposal_state::State::Withdrawn(
392 pb_governance::proposal_state::Withdrawn {
393 reason: truncate(&reason.reason, 1024).to_string(),
394 },
395 ));
396 }
397 pb_governance::proposal_state::State::Voting(_) => {}
398 pb_governance::proposal_state::State::Finished(ref outcome) => match outcome
399 .outcome
400 .clone()
401 .expect("proposal outcome always set")
402 .outcome
403 .expect("proposal outcome always set")
404 {
405 pb_governance::proposal_outcome::Outcome::Passed(_) => {}
406 pb_governance::proposal_outcome::Outcome::Failed(withdrawn) => {
407 match withdrawn.withdrawn {
408 None => {
409 }
411 Some(pb_governance::proposal_outcome::Withdrawn { reason }) => {
412 proposal_state.state =
414 Some(pb_governance::proposal_state::State::Finished(
415 pb_governance::proposal_state::Finished {
416 outcome: Some(pb_governance::ProposalOutcome{
417 outcome: Some(pb_governance::proposal_outcome::Outcome::Failed(
418 pb_governance::proposal_outcome::Failed {
419 withdrawn:
420 Some(pb_governance::proposal_outcome::Withdrawn {
421 reason: truncate(&reason, 1024)
422 .to_string(),
423 }),
424 },
425 )),
426 }),
427 },
428 ));
429 }
430 }
431 }
432 pb_governance::proposal_outcome::Outcome::Slashed(withdrawn) => {
433 match withdrawn.withdrawn {
434 None => {
435 }
437 Some(pb_governance::proposal_outcome::Withdrawn { reason }) => {
438 proposal_state.state = Some(pb_governance::proposal_state::State::Finished(
440 pb_governance::proposal_state::Finished {
441 outcome: Some(pb_governance::ProposalOutcome{
442 outcome: Some(pb_governance::proposal_outcome::Outcome::Slashed(
443 pb_governance::proposal_outcome::Slashed {
444 withdrawn:
445 Some(pb_governance::proposal_outcome::Withdrawn {
446 reason: truncate(&reason, 1024)
447 .to_string(),
448 }),
449 },
450 )),
451 }),
452 },
453 ));
454 }
455 }
456 }
457 },
458 pb_governance::proposal_state::State::Claimed(ref outcome) => match outcome
459 .outcome
460 .clone()
461 .expect("outcome is set")
462 .outcome
463 .expect("outcome is set")
464 {
465 pb_governance::proposal_outcome::Outcome::Passed(_) => {}
466 pb_governance::proposal_outcome::Outcome::Failed(withdrawn) => {
467 match withdrawn.withdrawn {
468 None => {
469 }
471 Some(pb_governance::proposal_outcome::Withdrawn { reason }) => {
472 proposal_state.state = Some(pb_governance::proposal_state::State::Claimed(
474 pb_governance::proposal_state::Claimed {
475 outcome: Some(pb_governance::ProposalOutcome{
476 outcome: Some(pb_governance::proposal_outcome::Outcome::Failed(
477 pb_governance::proposal_outcome::Failed{
478 withdrawn:
479 Some(pb_governance::proposal_outcome::Withdrawn {
480 reason: truncate(&reason, 1024)
481 .to_string(),
482 }),
483 },
484 )),
485 }),
486 },
487 ));
488 }
489 }
490 }
491 pb_governance::proposal_outcome::Outcome::Slashed(withdrawn) => {
492 match withdrawn.withdrawn {
493 None => {
494 }
496 Some(pb_governance::proposal_outcome::Withdrawn { reason }) => {
497 proposal_state.state = Some(pb_governance::proposal_state::State::Claimed(
498 pb_governance::proposal_state::Claimed {
499 outcome: Some(pb_governance::ProposalOutcome{
500 outcome: Some(pb_governance::proposal_outcome::Outcome::Slashed(
501 pb_governance::proposal_outcome::Slashed{
502 withdrawn:
503 Some(pb_governance::proposal_outcome::Withdrawn {
504 reason: truncate(&reason, 1024)
505 .to_string(),
506 }),
507 },
508 )),
509 }),
510 },
511 ));
512 }
513 }
514 }
515 },
516 }
517
518 tracing::info!(
520 "put key {:?}",
521 penumbra_sdk_governance::state_key::proposal_state(proposal_id)
522 );
523 let proposal_state: ProposalState = proposal_state.try_into()?;
524 delta.put(
525 penumbra_sdk_governance::state_key::proposal_state(proposal_id),
526 proposal_state,
527 );
528 }
529 Ok(())
530}
531
532#[inline]
539pub fn floor_char_boundary(s: &str, index: usize) -> usize {
540 if index >= s.len() {
541 s.len()
542 } else {
543 let lower_bound = index.saturating_sub(3);
544 let new_index = s.as_bytes()[lower_bound..=index]
545 .iter()
546 .rposition(|b| is_utf8_char_boundary(*b));
547
548 lower_bound + new_index.unwrap()
550 }
551}
552
553#[inline]
554pub(crate) const fn is_utf8_char_boundary(b: u8) -> bool {
555 (b as i8) >= -0x40
557}
558
559fn truncate(s: &str, max_bytes: usize) -> &str {
562 let closest = floor_char_boundary(s, max_bytes);
563
564 &s[..closest]
565}
566
567#[instrument]
575pub(crate) fn update_cometbft_mempool_settings(cometbft_home: PathBuf) -> anyhow::Result<()> {
576 let cometbft_config_path = cometbft_home.join("config").join("config.toml");
577 tracing::debug!(cometbft_config_path = %cometbft_config_path.display(), "opening cometbft config file");
578 let mut cometbft_config = TendermintConfig::load_toml_file(&cometbft_config_path)
579 .context("failed to load pre-migration cometbft config file")?;
580 let desired_max_txs_bytes = 10485760;
582 let desired_max_tx_bytes = 30720;
583 cometbft_config.mempool.max_txs_bytes = desired_max_txs_bytes;
585 cometbft_config.mempool.max_tx_bytes = desired_max_tx_bytes;
586 let mut fh = OpenOptions::new()
588 .create(false)
589 .write(true)
590 .truncate(true)
591 .open(cometbft_config_path.clone())
592 .context("failed to open cometbft config file for writing")?;
593 fh.write_all(toml::to_string(&cometbft_config)?.as_bytes())
594 .context("failed to write updated cometbft config to toml file")?;
595 Ok(())
596}
597
598mod tests {
599 #[test]
600 fn truncation() {
601 use super::truncate;
602 let s = "Hello, world!";
603
604 assert_eq!(truncate(s, 5), "Hello");
605
606 let s = "โค๏ธ๐งก๐๐๐๐";
607 assert_eq!(s.len(), 26);
608 assert_eq!("โค".len(), 3);
609
610 assert_eq!(truncate(s, 2), "");
611 assert_eq!(truncate(s, 3), "โค");
612 assert_eq!(truncate(s, 4), "โค");
613 }
614}