Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 10ba300

Browse files
committedJun 10, 2024··
reverse migration refactoring
1 parent fe8a700 commit 10ba300

File tree

3 files changed

+215
-78
lines changed

3 files changed

+215
-78
lines changed
 

‎crates/bin/pd/src/migrate.rs

+9-63
Original file line numberDiff line numberDiff line change
@@ -99,77 +99,23 @@ impl Migration {
9999
.expect("chain state has a block height");
100100
let post_upgrade_height = pre_upgrade_height.wrapping_add(1);
101101

102-
let migration_duration = match self {
103-
Migration::SimpleMigration => simple::migrate(storage.clone()).await?,
104-
105-
Migration::Testnet78 => testnet78::migrate(storage.clone()).await?,
102+
match self {
103+
Migration::SimpleMigration => {
104+
simple::migrate(storage, pd_home.clone(), genesis_start).await?
105+
}
106+
107+
Migration::Testnet78 => {
108+
testnet78::migrate(storage, pd_home.clone(), genesis_start).await?
109+
}
106110
_ => unreachable!(),
107-
};
108-
109-
// There are some common operations that need to take place post-migration.
110-
// Get a new `StateDelta` post-migration:
111-
let post_upgrade_state = storage.latest_snapshot();
112-
let mut delta = StateDelta::new(post_upgrade_state);
113-
114-
// Set halt bit to 0, so chain can start again.
115-
delta.ready_to_start();
116-
117-
// Set the block height to 0, as upgrades should start at block 0.
118-
delta.put_block_height(0u64);
119-
let chain_id = delta.get_chain_id().await?;
120-
let post_upgrade_root_hash = storage
121-
.commit_in_place(delta)
122-
.await
123-
.context("failed to reset halt bit")?;
124-
125-
storage.release().await;
126-
127-
// The migration is complete, now we need to generate a genesis file. To do this, we need
128-
// to lookup a validator view from the chain, and specify the post-upgrade app hash and
129-
// initial height.
130-
let app_state = penumbra_app::genesis::Content {
131-
chain_id,
132-
..Default::default()
133-
};
134-
let mut genesis = TestnetConfig::make_genesis(app_state.clone()).expect("can make genesis");
135-
genesis.app_hash = post_upgrade_root_hash
136-
.0
137-
.to_vec()
138-
.try_into()
139-
.expect("infallible conversion");
140-
genesis.initial_height = post_upgrade_height as i64;
141-
genesis.genesis_time = genesis_start.unwrap_or_else(|| {
142-
let now = tendermint::time::Time::now();
143-
tracing::info!(%now, "no genesis time provided, detecting a testing setup");
144-
now
145-
});
146-
let checkpoint = post_upgrade_root_hash.0.to_vec();
147-
let genesis = TestnetConfig::make_checkpoint(genesis, Some(checkpoint));
148-
let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
149-
tracing::info!("genesis: {}", genesis_json);
150-
let genesis_path = pd_home.join("genesis.json");
151-
std::fs::write(genesis_path, genesis_json).expect("can write genesis");
152-
153-
let validator_state_path = pd_home.join("priv_validator_state.json");
154-
let fresh_validator_state = crate::testnet::generate::TestnetValidator::initial_state();
155-
std::fs::write(validator_state_path, fresh_validator_state)
156-
.expect("can write validator state");
111+
}
157112

158113
if let Some(comet_home) = comet_home {
159114
// TODO avoid this when refactoring to clean up migrations
160115
let genesis_path = pd_home.join("genesis.json");
161116
migrate_comet_data(comet_home, genesis_path).await?;
162117
}
163118

164-
tracing::info!(
165-
pre_upgrade_height,
166-
post_upgrade_height,
167-
?pre_upgrade_root_hash,
168-
?post_upgrade_root_hash,
169-
duration = migration_duration.as_secs(),
170-
"successful migration!"
171-
);
172-
173119
Ok(())
174120
}
175121
}

‎crates/bin/pd/src/migrate/simple.rs

+53-6
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,72 @@
33
use anyhow;
44
use cnidarium::{StateDelta, StateWrite, Storage};
55
use jmt::RootHash;
6-
use penumbra_sct::component::clock::EpochManager;
7-
use std::time::Duration;
6+
use penumbra_app::app::StateReadExt as _;
7+
use penumbra_sct::component::clock::{EpochManager, EpochRead};
8+
use std::path::PathBuf;
89

9-
pub async fn migrate(storage: Storage) -> anyhow::Result<Duration> {
10+
use crate::testnet::generate::TestnetConfig;
11+
pub async fn migrate(
12+
storage: Storage,
13+
path_to_export: PathBuf,
14+
genesis_start: Option<tendermint::time::Time>,
15+
) -> anyhow::Result<()> {
1016
let export_state = storage.latest_snapshot();
1117
let root_hash = export_state.root_hash().await.expect("can get root hash");
18+
let height = export_state
19+
.get_block_height()
20+
.await
21+
.expect("can get block height");
1222
let app_hash_pre_migration: RootHash = root_hash.into();
23+
let post_ugprade_height = height.wrapping_add(1);
1324

1425
/* --------- writing to the jmt ------------ */
1526
tracing::info!(?app_hash_pre_migration, "app hash pre-upgrade");
1627
let mut delta = StateDelta::new(export_state);
17-
let start_time = std::time::SystemTime::now();
1828
delta.put_raw(
1929
"banana".to_string(),
2030
"a good fruit (and migration works!)".into(),
2131
);
2232
delta.put_block_height(0u64);
33+
let root_hash = storage.commit_in_place(delta).await?;
34+
let app_hash_post_migration: RootHash = root_hash.into();
35+
tracing::info!(?app_hash_post_migration, "app hash post upgrade");
2336

24-
storage.commit_in_place(delta).await?;
37+
/* --------- collecting genesis data -------- */
38+
tracing::info!("generating genesis");
39+
let migrated_state = storage.latest_snapshot();
40+
let root_hash = migrated_state.root_hash().await.expect("can get root hash");
41+
let app_hash: RootHash = root_hash.into();
42+
tracing::info!(?root_hash, "root hash from snapshot (post-upgrade)");
2543

26-
Ok(start_time.elapsed().expect("start time not set"))
44+
/* ---------- generate genesis ------------ */
45+
let chain_id = migrated_state.get_chain_id().await?;
46+
let app_state = penumbra_app::genesis::Content {
47+
chain_id,
48+
..Default::default()
49+
};
50+
let mut genesis = TestnetConfig::make_genesis(app_state.clone()).expect("can make genesis");
51+
genesis.app_hash = app_hash
52+
.0
53+
.to_vec()
54+
.try_into()
55+
.expect("infaillible conversion");
56+
genesis.initial_height = post_ugprade_height as i64;
57+
genesis.genesis_time = genesis_start.unwrap_or_else(|| {
58+
let now = tendermint::time::Time::now();
59+
tracing::info!(%now, "no genesis time provided, detecting a testing setup");
60+
now
61+
});
62+
let checkpoint = app_hash.0.to_vec();
63+
let genesis = TestnetConfig::make_checkpoint(genesis, Some(checkpoint));
64+
65+
let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
66+
tracing::info!("genesis: {}", genesis_json);
67+
let genesis_path = path_to_export.join("genesis.json");
68+
std::fs::write(genesis_path, genesis_json).expect("can write genesis");
69+
70+
let validator_state_path = path_to_export.join("priv_validator_state.json");
71+
let fresh_validator_state = crate::testnet::generate::TestnetValidator::initial_state();
72+
std::fs::write(validator_state_path, fresh_validator_state).expect("can write validator state");
73+
Ok(())
2774
}

‎crates/bin/pd/src/migrate/testnet78.rs

+153-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
//! Contains functions related to the migration script of Testnet78.
2-
use cnidarium::{StateDelta, Storage};
3-
use std::time::Duration;
2+
use cnidarium::{Snapshot, StateDelta, Storage};
3+
use futures::TryStreamExt as _;
4+
use jmt::RootHash;
5+
use pbjson_types::Any;
6+
use penumbra_app::app::StateReadExt as _;
7+
use penumbra_proto::{DomainType as _, StateReadProto as _, StateWriteProto as _};
8+
use penumbra_sct::component::clock::EpochRead as _;
9+
use penumbra_stake::validator::Validator;
10+
use std::{path::PathBuf, time::Duration};
411
use tracing::instrument;
512

13+
use crate::testnet::generate::TestnetConfig;
14+
615
/// Run the full migration, given an export path and a start time for genesis.
716
///
817
/// Menu:
918
/// - Truncate various user-supplied `String` fields to a maximum length.
10-
/// * Validator Definitions:
19+
/// * Validators:
1120
/// - `name` (140 characters)
1221
/// - `website` (70 characters)
1322
/// - `description` (280 characters)
@@ -25,19 +34,154 @@ use tracing::instrument;
2534
/// * Signaling Proposals:
2635
/// - `commit hash` (64 characters)
2736
#[instrument]
28-
pub async fn migrate(storage: Storage) -> anyhow::Result<Duration> {
37+
pub async fn migrate(
38+
storage: Storage,
39+
pd_home: PathBuf,
40+
genesis_start: Option<tendermint::time::Time>,
41+
) -> anyhow::Result<()> {
2942
// Setup:
3043
let initial_state = storage.latest_snapshot();
44+
let chain_id = initial_state.get_chain_id().await?;
45+
let root_hash = initial_state
46+
.root_hash()
47+
.await
48+
.expect("chain state has a root hash");
49+
let pre_upgrade_root_hash: RootHash = root_hash.into();
50+
let pre_upgrade_height = initial_state
51+
.get_block_height()
52+
.await
53+
.expect("chain state has a block height");
54+
let post_upgrade_height = pre_upgrade_height.wrapping_add(1);
3155

3256
// We initialize a `StateDelta` and start by reaching into the JMT for all entries matching the
3357
// swap execution prefix. Then, we write each entry to the nv-storage.
3458
let mut delta = StateDelta::new(initial_state);
35-
let start_time = std::time::SystemTime::now();
59+
let (migration_duration, post_upgrade_root_hash) = {
60+
let start_time = std::time::SystemTime::now();
61+
// Adjust the length of `Validator` fields.
62+
truncate_validator_fields(&mut delta).await?;
63+
64+
let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
65+
tracing::info!(?post_upgrade_root_hash, "post-migration root hash");
66+
67+
(
68+
start_time.elapsed().expect("start time not set"),
69+
post_upgrade_root_hash,
70+
)
71+
};
72+
storage.release().await;
73+
74+
// The migration is complete, now we need to generate a genesis file. To do this, we need
75+
// to lookup a validator view from the chain, and specify the post-upgrade app hash and
76+
// initial height.
77+
let app_state = penumbra_app::genesis::Content {
78+
chain_id,
79+
..Default::default()
80+
};
81+
let mut genesis = TestnetConfig::make_genesis(app_state.clone()).expect("can make genesis");
82+
genesis.app_hash = post_upgrade_root_hash
83+
.0
84+
.to_vec()
85+
.try_into()
86+
.expect("infaillible conversion");
87+
genesis.initial_height = post_upgrade_height as i64;
88+
genesis.genesis_time = genesis_start.unwrap_or_else(|| {
89+
let now = tendermint::time::Time::now();
90+
tracing::info!(%now, "no genesis time provided, detecting a testing setup");
91+
now
92+
});
93+
let checkpoint = post_upgrade_root_hash.0.to_vec();
94+
let genesis = TestnetConfig::make_checkpoint(genesis, Some(checkpoint));
95+
96+
let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
97+
tracing::info!("genesis: {}", genesis_json);
98+
let genesis_path = pd_home.join("genesis.json");
99+
std::fs::write(genesis_path, genesis_json).expect("can write genesis");
100+
101+
let validator_state_path = pd_home.join("priv_validator_state.json");
102+
103+
let fresh_validator_state = crate::testnet::generate::TestnetValidator::initial_state();
104+
std::fs::write(validator_state_path, fresh_validator_state).expect("can write validator state");
105+
106+
tracing::info!(
107+
pre_upgrade_height,
108+
post_upgrade_height,
109+
?pre_upgrade_root_hash,
110+
?post_upgrade_root_hash,
111+
duration = migration_duration.as_secs(),
112+
"successful migration!"
113+
);
114+
115+
Ok(())
116+
}
117+
118+
async fn truncate_validator_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
119+
let key_prefix_validators = penumbra_stake::state_key::validators::definitions::prefix();
120+
let all_validators = delta
121+
.prefix_proto::<Any>(&key_prefix_validators)
122+
.map_ok(|(k, v)| (k, Validator::decode(v.value).expect("only validators")))
123+
.try_collect::<Vec<(String, Validator)>>()
124+
.await?;
125+
126+
for (key, mut validator) in all_validators {
127+
validator.name = truncate(&validator.name, 140).to_string();
128+
129+
delta.put(key, validator);
130+
}
131+
132+
Ok(())
133+
}
134+
135+
// Since the limits are based on `String::len`, which returns
136+
// the number of bytes, we need to truncate the UTF-8 strings at the
137+
// correct byte boundaries.
138+
//
139+
// This can be simplified once https://github.com/rust-lang/rust/issues/93743
140+
// is stabilized.
141+
#[inline]
142+
pub fn floor_char_boundary(s: &str, index: usize) -> usize {
143+
if index >= s.len() {
144+
s.len()
145+
} else {
146+
let lower_bound = index.saturating_sub(3);
147+
let new_index = s.as_bytes()[lower_bound..=index]
148+
.iter()
149+
.rposition(|b| is_utf8_char_boundary(*b));
150+
151+
// SAFETY: we know that the character boundary will be within four bytes
152+
unsafe { lower_bound + new_index.unwrap_unchecked() }
153+
}
154+
}
155+
156+
#[inline]
157+
pub(crate) const fn is_utf8_char_boundary(b: u8) -> bool {
158+
// This is bit magic equivalent to: b < 128 || b >= 192
159+
(b as i8) >= -0x40
160+
}
161+
162+
// Truncates a utf-8 string to the nearest character boundary,
163+
// not exceeding max_bytes
164+
fn truncate(s: &str, max_bytes: usize) -> &str {
165+
let closest = floor_char_boundary(s, max_bytes);
166+
167+
&s[..closest]
168+
}
169+
170+
mod tests {
171+
use super::*;
172+
173+
#[test]
174+
fn truncation() {
175+
let s = "Hello, world!";
36176

37-
// TODO: adjust data lengths here
177+
assert_eq!(truncate(s, 5), "Hello");
38178

39-
let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
40-
tracing::info!(?post_upgrade_root_hash, "post-migration root hash");
179+
let s = "❤️🧡💛💚💙💜";
180+
assert_eq!(s.len(), 26);
181+
assert_eq!("❤".len(), 3);
41182

42-
Ok(start_time.elapsed().expect("start time not set"))
183+
assert_eq!(truncate(s, 2), "");
184+
assert_eq!(truncate(s, 3), "❤");
185+
assert_eq!(truncate(s, 4), "❤");
186+
}
43187
}

0 commit comments

Comments
 (0)
Please sign in to comment.