Skip to content

Commit e281cbd

Browse files
committed
Finish migration, PR review feedback
1 parent 10ba300 commit e281cbd

File tree

7 files changed

+254
-76
lines changed

7 files changed

+254
-76
lines changed

crates/bin/pd/src/migrate.rs

+3-20
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,18 @@ mod testnet77;
1313
mod testnet78;
1414

1515
use anyhow::{ensure, Context};
16-
use jmt::RootHash;
17-
use penumbra_app::app::StateReadExt as _;
18-
use penumbra_governance::{StateReadExt, StateWriteExt as _};
19-
use penumbra_sct::component::clock::{EpochManager as _, EpochRead};
16+
use penumbra_governance::StateReadExt;
17+
use penumbra_sct::component::clock::EpochRead;
2018
use std::path::{Path, PathBuf};
2119
use tracing::instrument;
2220

23-
use cnidarium::{StateDelta, Storage};
21+
use cnidarium::Storage;
2422
use penumbra_app::SUBSTORE_PREFIXES;
2523

2624
use flate2::write::GzEncoder;
2725
use flate2::Compression;
2826
use std::fs::File;
2927

30-
use crate::testnet::generate::TestnetConfig;
31-
3228
/// The kind of migration that should be performed.
3329
#[derive(Debug)]
3430
pub enum Migration {
@@ -86,19 +82,6 @@ impl Migration {
8682
return Ok(());
8783
}
8884

89-
// Collect various pieces of state pre-migration:
90-
let initial_state = storage.latest_snapshot();
91-
let root_hash = initial_state
92-
.root_hash()
93-
.await
94-
.expect("chain state has a root hash");
95-
let pre_upgrade_root_hash: RootHash = root_hash.into();
96-
let pre_upgrade_height = initial_state
97-
.get_block_height()
98-
.await
99-
.expect("chain state has a block height");
100-
let post_upgrade_height = pre_upgrade_height.wrapping_add(1);
101-
10285
match self {
10386
Migration::SimpleMigration => {
10487
simple::migrate(storage, pd_home.clone(), genesis_start).await?

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

+219-14
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use futures::TryStreamExt as _;
44
use jmt::RootHash;
55
use pbjson_types::Any;
66
use penumbra_app::app::StateReadExt as _;
7+
use penumbra_governance::StateReadExt as _;
78
use penumbra_proto::{DomainType as _, StateReadProto as _, StateWriteProto as _};
89
use penumbra_sct::component::clock::EpochRead as _;
910
use penumbra_stake::validator::Validator;
10-
use std::{path::PathBuf, time::Duration};
11+
use std::path::PathBuf;
1112
use tracing::instrument;
1213

1314
use crate::testnet::generate::TestnetConfig;
@@ -17,22 +18,24 @@ use crate::testnet::generate::TestnetConfig;
1718
/// Menu:
1819
/// - Truncate various user-supplied `String` fields to a maximum length.
1920
/// * Validators:
20-
/// - `name` (140 characters)
21-
/// - `website` (70 characters)
22-
/// - `description` (280 characters)
21+
/// - `name` (140 bytes)
22+
/// - `website` (70 bytes)
23+
/// - `description` (280 bytes)
2324
/// * Governance Parameter Changes:
24-
/// - `key` (64 characters)
25-
/// - `value` (2048 characters)
26-
/// - `component` (64 characters)
25+
/// - `key` (64 bytes)
26+
/// - `value` (2048 bytes)
27+
/// - `component` (64 bytes)
2728
/// * Governance Proposals:
28-
/// - `title` (80 characters)
29-
/// - `description` (10,000 characters)
29+
/// - `title` (80 bytes)
30+
/// - `description` (10,000 bytes)
3031
/// * Governance Proposal Withdrawals:
31-
/// - `reason` (1024 characters)
32+
/// - `reason` (1024 bytes)
3233
/// * Governance IBC Client Freeze Proposals:
33-
/// - `client_id` (128 characters)
34-
/// * Signaling Proposals:
35-
/// - `commit hash` (64 characters)
34+
/// - `client_id` (128 bytes)
35+
/// * Governance IBC Client Unfreeze Proposals:
36+
/// - `client_id` (128 bytes)
37+
/// * Governance Signaling Proposals:
38+
/// - `commit hash` (255 bytes)
3639
#[instrument]
3740
pub async fn migrate(
3841
storage: Storage,
@@ -61,6 +64,12 @@ pub async fn migrate(
6164
// Adjust the length of `Validator` fields.
6265
truncate_validator_fields(&mut delta).await?;
6366

67+
// Adjust the length of governance proposal fields.
68+
truncate_proposal_fields(&mut delta).await?;
69+
70+
// Adjust the length of governance proposal outcome fields.
71+
truncate_proposal_outcome_fields(&mut delta).await?;
72+
6473
let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
6574
tracing::info!(?post_upgrade_root_hash, "post-migration root hash");
6675

@@ -115,6 +124,10 @@ pub async fn migrate(
115124
Ok(())
116125
}
117126

127+
/// * Validators:
128+
/// - `name` (140 bytes)
129+
/// - `website` (70 bytes)
130+
/// - `description` (280 bytes)
118131
async fn truncate_validator_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
119132
let key_prefix_validators = penumbra_stake::state_key::validators::definitions::prefix();
120133
let all_validators = delta
@@ -125,13 +138,205 @@ async fn truncate_validator_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::
125138

126139
for (key, mut validator) in all_validators {
127140
validator.name = truncate(&validator.name, 140).to_string();
141+
validator.website = truncate(&validator.website, 70).to_string();
142+
validator.description = truncate(&validator.description, 280).to_string();
128143

129144
delta.put(key, validator);
130145
}
131146

132147
Ok(())
133148
}
134149

150+
/// * Governance Proposals:
151+
/// - `title` (80 bytes)
152+
/// - `description` (10,000 bytes)
153+
/// * Governance Parameter Changes:
154+
/// - `key` (64 bytes)
155+
/// - `value` (2048 bytes)
156+
/// - `component` (64 bytes)
157+
/// * Governance IBC Client Freeze Proposals:
158+
/// - `client_id` (128 bytes)
159+
/// * Governance IBC Client Unfreeze Proposals:
160+
/// - `client_id` (128 bytes)
161+
/// * Governance Signaling Proposals:
162+
/// - `commit hash` (255 bytes)
163+
async fn truncate_proposal_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
164+
let next_proposal_id: u64 = delta.next_proposal_id().await?;
165+
166+
// Range each proposal and truncate the fields.
167+
for proposal_id in 0..next_proposal_id {
168+
let proposal = delta.proposal_definition(proposal_id).await?;
169+
170+
if proposal.is_none() {
171+
break;
172+
}
173+
174+
let mut proposal = proposal.unwrap();
175+
176+
proposal.title = truncate(&proposal.title, 80).to_string();
177+
proposal.description = truncate(&proposal.description, 10_000).to_string();
178+
179+
// Depending on the proposal type, we may need to truncate additional fields.
180+
match proposal.payload {
181+
penumbra_governance::ProposalPayload::Signaling { commit } => {
182+
proposal.payload = penumbra_governance::ProposalPayload::Signaling {
183+
commit: commit.map(|commit| truncate(&commit, 255).to_string()),
184+
};
185+
}
186+
penumbra_governance::ProposalPayload::Emergency { halt_chain: _ } => {}
187+
penumbra_governance::ProposalPayload::ParameterChange(mut param_change) => {
188+
for (i, mut change) in param_change.changes.clone().into_iter().enumerate() {
189+
let key = truncate(&change.key, 64).to_string();
190+
let value = truncate(&change.value, 2048).to_string();
191+
let component = truncate(&change.component, 64).to_string();
192+
193+
change.key = key;
194+
change.value = value;
195+
change.component = component;
196+
197+
param_change.changes[i] = change;
198+
}
199+
200+
for (i, mut change) in param_change.preconditions.clone().into_iter().enumerate() {
201+
let key = truncate(&change.key, 64).to_string();
202+
let value = truncate(&change.value, 2048).to_string();
203+
let component = truncate(&change.component, 64).to_string();
204+
205+
change.key = key;
206+
change.value = value;
207+
change.component = component;
208+
209+
param_change.preconditions[i] = change;
210+
}
211+
212+
proposal.payload =
213+
penumbra_governance::ProposalPayload::ParameterChange(param_change);
214+
}
215+
penumbra_governance::ProposalPayload::CommunityPoolSpend {
216+
transaction_plan: _,
217+
} => {}
218+
penumbra_governance::ProposalPayload::UpgradePlan { height: _ } => {}
219+
penumbra_governance::ProposalPayload::FreezeIbcClient { client_id } => {
220+
proposal.payload = penumbra_governance::ProposalPayload::FreezeIbcClient {
221+
client_id: truncate(&client_id, 128).to_string(),
222+
};
223+
}
224+
penumbra_governance::ProposalPayload::UnfreezeIbcClient { client_id } => {
225+
proposal.payload = penumbra_governance::ProposalPayload::UnfreezeIbcClient {
226+
client_id: truncate(&client_id, 128).to_string(),
227+
};
228+
}
229+
};
230+
231+
// Store the truncated proposal data
232+
delta.put(
233+
penumbra_governance::state_key::proposal_definition(proposal_id),
234+
proposal.clone(),
235+
);
236+
}
237+
238+
Ok(())
239+
}
240+
241+
/// * Governance Proposal Withdrawals:
242+
/// - `reason` (1024 bytes)
243+
async fn truncate_proposal_outcome_fields(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
244+
let next_proposal_id: u64 = delta.next_proposal_id().await?;
245+
246+
// Range each proposal outcome and truncate the fields.
247+
for proposal_id in 0..next_proposal_id {
248+
let proposal_state = delta.proposal_state(proposal_id).await?;
249+
250+
if proposal_state.is_none() {
251+
break;
252+
}
253+
254+
let mut proposal_state = proposal_state.unwrap();
255+
256+
match proposal_state {
257+
penumbra_governance::proposal_state::State::Withdrawn { reason } => {
258+
proposal_state = penumbra_governance::proposal_state::State::Withdrawn {
259+
reason: truncate(&reason, 1024).to_string(),
260+
};
261+
}
262+
penumbra_governance::proposal_state::State::Voting => {}
263+
penumbra_governance::proposal_state::State::Finished { ref outcome } => match outcome {
264+
penumbra_governance::proposal_state::Outcome::Passed => {}
265+
penumbra_governance::proposal_state::Outcome::Failed { withdrawn } => {
266+
match withdrawn {
267+
penumbra_governance::proposal_state::Withdrawn::No => {}
268+
penumbra_governance::proposal_state::Withdrawn::WithReason { reason } => {
269+
proposal_state = penumbra_governance::proposal_state::State::Finished {
270+
outcome: penumbra_governance::proposal_state::Outcome::Failed {
271+
withdrawn:
272+
penumbra_governance::proposal_state::Withdrawn::WithReason {
273+
reason: truncate(&reason, 1024).to_string(),
274+
},
275+
},
276+
};
277+
}
278+
}
279+
}
280+
penumbra_governance::proposal_state::Outcome::Slashed { withdrawn } => {
281+
match withdrawn {
282+
penumbra_governance::proposal_state::Withdrawn::No => {}
283+
penumbra_governance::proposal_state::Withdrawn::WithReason { reason } => {
284+
proposal_state = penumbra_governance::proposal_state::State::Finished {
285+
outcome: penumbra_governance::proposal_state::Outcome::Slashed {
286+
withdrawn:
287+
penumbra_governance::proposal_state::Withdrawn::WithReason {
288+
reason: truncate(&reason, 1024).to_string(),
289+
},
290+
},
291+
};
292+
}
293+
}
294+
}
295+
},
296+
penumbra_governance::proposal_state::State::Claimed { ref outcome } => match outcome {
297+
penumbra_governance::proposal_state::Outcome::Passed => {}
298+
penumbra_governance::proposal_state::Outcome::Failed { withdrawn } => {
299+
match withdrawn {
300+
penumbra_governance::proposal_state::Withdrawn::No => {}
301+
penumbra_governance::proposal_state::Withdrawn::WithReason { reason } => {
302+
proposal_state = penumbra_governance::proposal_state::State::Claimed {
303+
outcome: penumbra_governance::proposal_state::Outcome::Failed {
304+
withdrawn:
305+
penumbra_governance::proposal_state::Withdrawn::WithReason {
306+
reason: truncate(&reason, 1024).to_string(),
307+
},
308+
},
309+
};
310+
}
311+
}
312+
}
313+
penumbra_governance::proposal_state::Outcome::Slashed { withdrawn } => {
314+
match withdrawn {
315+
penumbra_governance::proposal_state::Withdrawn::No => {}
316+
penumbra_governance::proposal_state::Withdrawn::WithReason { reason } => {
317+
proposal_state = penumbra_governance::proposal_state::State::Claimed {
318+
outcome: penumbra_governance::proposal_state::Outcome::Slashed {
319+
withdrawn:
320+
penumbra_governance::proposal_state::Withdrawn::WithReason {
321+
reason: truncate(&reason, 1024).to_string(),
322+
},
323+
},
324+
};
325+
}
326+
}
327+
}
328+
},
329+
}
330+
331+
// Store the truncated proposal state data
332+
delta.put(
333+
penumbra_governance::state_key::proposal_state(proposal_id),
334+
proposal_state.clone(),
335+
);
336+
}
337+
Ok(())
338+
}
339+
135340
// Since the limits are based on `String::len`, which returns
136341
// the number of bytes, we need to truncate the UTF-8 strings at the
137342
// correct byte boundaries.
@@ -168,10 +373,10 @@ fn truncate(s: &str, max_bytes: usize) -> &str {
168373
}
169374

170375
mod tests {
171-
use super::*;
172376

173377
#[test]
174378
fn truncation() {
379+
use super::truncate;
175380
let s = "Hello, world!";
176381

177382
assert_eq!(truncate(s, 5), "Hello");

crates/bin/pd/src/testnet/generate.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -573,19 +573,19 @@ impl TryFrom<&TestnetValidator> for Validator {
573573
type Error = anyhow::Error;
574574
fn try_from(tv: &TestnetValidator) -> anyhow::Result<Validator> {
575575
// Validation:
576-
// - Website has a max length of 70 chars
576+
// - Website has a max length of 70 bytes
577577
if tv.website.len() > 70 {
578-
anyhow::bail!("validator website field must be less than 70 characters");
578+
anyhow::bail!("validator website field must be less than 70 bytes");
579579
}
580580

581-
// - Name has a max length of 140 chars
581+
// - Name has a max length of 140 bytes
582582
if tv.name.len() > 140 {
583-
anyhow::bail!("validator name must be less than 140 characters");
583+
anyhow::bail!("validator name must be less than 140 bytes");
584584
}
585585

586-
// - Description has a max length of 280 chars
586+
// - Description has a max length of 280 bytes
587587
if tv.description.len() > 280 {
588-
anyhow::bail!("validator description must be less than 280 characters");
588+
anyhow::bail!("validator description must be less than 280 bytes");
589589
}
590590

591591
Ok(Validator {

crates/core/component/governance/src/proposal.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ impl TryFrom<pb::Proposal> for Proposal {
106106
commit: if signaling.commit.is_empty() {
107107
None
108108
} else {
109-
// Commit hash has max length of 64 chars:
110-
if signaling.commit.len() > 64 {
111-
anyhow::bail!("proposal commit hash must be less than 64 characters");
109+
// Commit hash has max length of 255 bytes:
110+
if signaling.commit.len() > 255 {
111+
anyhow::bail!("proposal commit hash must be less than 255 bytes");
112112
}
113113

114114
Some(signaling.commit)
@@ -140,15 +140,19 @@ impl TryFrom<pb::Proposal> for Proposal {
140140
height: upgrade_plan.height,
141141
},
142142
Payload::FreezeIbcClient(freeze_ibc_client) => {
143-
// Validation: client ID has a max length of 128 chars
143+
// Validation: client ID has a max length of 128 bytes
144144
if freeze_ibc_client.client_id.len() > 128 {
145-
anyhow::bail!("client ID must be less than 128 characters");
145+
anyhow::bail!("client ID must be less than 128 bytes");
146146
}
147147
ProposalPayload::FreezeIbcClient {
148148
client_id: freeze_ibc_client.client_id,
149149
}
150150
}
151151
Payload::UnfreezeIbcClient(unfreeze_ibc_client) => {
152+
// Validation: client ID has a max length of 128 bytes
153+
if unfreeze_ibc_client.client_id.len() > 128 {
154+
anyhow::bail!("client ID must be less than 128 bytes");
155+
}
152156
ProposalPayload::UnfreezeIbcClient {
153157
client_id: unfreeze_ibc_client.client_id,
154158
}

0 commit comments

Comments
 (0)