Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

penumbra: do not merge - 71 with swap executions #4120

Closed
wants to merge 8 commits into from
4 changes: 2 additions & 2 deletions crates/bin/pd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use cnidarium::{StateDelta, Storage};
use metrics_exporter_prometheus::PrometheusBuilder;
use pd::{
cli::{Opt, RootCommand, TestnetCommand},
migrate::Migration::SimpleMigration,
migrate::Migration::Testnet70,
testnet::{
config::{get_testnet_dir, parse_tm_address, url_has_necessary_parts},
generate::TestnetConfig,
Expand Down Expand Up @@ -442,7 +442,7 @@ async fn main() -> anyhow::Result<()> {
migrate_archive,
} => {
tracing::info!("migrating state in {}", target_directory.display());
SimpleMigration
Testnet70
.migrate(target_directory.clone(), genesis_start)
.await
.context("failed to upgrade state")?;
Expand Down
105 changes: 92 additions & 13 deletions crates/bin/pd/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
//! This module declares how local `pd` state should be altered, if at all,
//! in order to be compatible with the network post-chain-upgrade.
use anyhow::Context;
use futures::StreamExt as _;
use std::path::PathBuf;

use cnidarium::{StateDelta, StateWrite, Storage};
use cnidarium::{StateDelta, StateRead, StateWrite, Storage};
use jmt::RootHash;
use penumbra_app::{app::StateReadExt, SUBSTORE_PREFIXES};
use penumbra_sct::component::clock::{EpochManager, EpochRead};
use penumbra_stake::{
component::validator_handler::ValidatorDataRead, genesis::Content as StakeContent,
};

use crate::testnet::generate::TestnetConfig;

Expand All @@ -28,8 +26,8 @@ pub enum Migration {
/// A simple migration: adds a key to the consensus state.
/// This is useful for testing upgrade mechanisms, including in production.
SimpleMigration,
/// Migrates from testnet-64 to testnet-65.
Testnet65,
/// Testnet-70 migration: move swap executions from the jmt to nv-storage.
Testnet70,
}

impl Migration {
Expand Down Expand Up @@ -73,14 +71,8 @@ impl Migration {

/* ---------- generate genesis ------------ */
let chain_id = migrated_state.get_chain_id().await?;
let validators = migrated_state.validator_definitions().await?;
let app_state = penumbra_genesis::Content {
chain_id,
stake_content: StakeContent {
// TODO(erwan): See https://github.com/penumbra-zone/penumbra/issues/3846
validators: validators.into_iter().map(Into::into).collect(),
..Default::default()
},
..Default::default()
};
let mut genesis =
Expand Down Expand Up @@ -110,7 +102,94 @@ impl Migration {
std::fs::write(validator_state_path, fresh_validator_state)
.expect("can write validator state");
}
Migration::Testnet65 => { /* currently a no-op. */ }
Migration::Testnet70 => {
// Our goal is to fetch all swap executions from the jmt and store them in nv-storage.
// In particular, we want to make sure that client lookups for (height, trading pair)
// resolve to the same value as before.

// Setup:
let start_time = std::time::SystemTime::now();
let rocksdb_dir = path_to_export.join("rocksdb");
let storage =
Storage::load(rocksdb_dir.clone(), SUBSTORE_PREFIXES.to_vec()).await?;
let export_state = storage.latest_snapshot();
let root_hash = export_state.root_hash().await.expect("can get root hash");
let pre_upgrade_root_hash: RootHash = root_hash.into();
let pre_upgrade_height = export_state
.get_block_height()
.await
.expect("can get block height");
let post_upgrade_height = pre_upgrade_height.wrapping_add(1);

// We initialize a `StateDelta` and start by reaching into the JMT for all entries matching the
// swap execution prefix. Then, we write each entry to the nv-storage.
let mut delta = StateDelta::new(export_state);

let prefix_key = "dex/swap_execution/";
let mut swap_execution_stream = delta.prefix_raw(prefix_key);

while let Some(r) = swap_execution_stream.next().await {
let (key, swap_execution) = r?;
tracing::info!("migrating swap execution: {}", key);
delta.nonverifiable_put_raw(key.into_bytes(), swap_execution);
}

delta.put_block_height(0u64);

let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
tracing::info!(?post_upgrade_root_hash, "post-upgrade root hash");

let migration_duration = start_time.elapsed().unwrap();

// Reload storage so we can make reads against its migrated state:
storage.release().await;
let storage = Storage::load(rocksdb_dir, SUBSTORE_PREFIXES.to_vec()).await?;
let migrated_state = storage.latest_snapshot();

// The migration is complete, now we need to generate a genesis file. To do this, we need
// to lookup a validator view from the chain, and specify the post-upgrade app hash and
// initial height.
let chain_id = migrated_state.get_chain_id().await?;
let app_state = penumbra_genesis::Content {
chain_id,
..Default::default()
};
let mut genesis =
TestnetConfig::make_genesis(app_state.clone()).expect("can make genesis");
genesis.app_hash = post_upgrade_root_hash
.0
.to_vec()
.try_into()
.expect("infaillible conversion");
genesis.initial_height = post_upgrade_height as i64;
genesis.genesis_time = genesis_start.unwrap_or_else(|| {
let now = tendermint::time::Time::now();
tracing::info!(%now, "no genesis time provided, detecting a testing setup");
now
});
let checkpoint = post_upgrade_root_hash.0.to_vec();
let genesis = TestnetConfig::make_checkpoint(genesis, Some(checkpoint));

let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
tracing::info!("genesis: {}", genesis_json);
let genesis_path = path_to_export.join("genesis.json");
std::fs::write(genesis_path, genesis_json).expect("can write genesis");

let validator_state_path = path_to_export.join("priv_validator_state.json");
let fresh_validator_state =
crate::testnet::generate::TestnetValidator::initial_state();
std::fs::write(validator_state_path, fresh_validator_state)
.expect("can write validator state");

tracing::info!(
pre_upgrade_height,
post_upgrade_height,
?pre_upgrade_root_hash,
?post_upgrade_root_hash,
duration = migration_duration.as_secs(),
"successful migration!"
);
}
}
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/app/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ impl App {
///
/// Increment this manually after fixing the root cause for a chain halt: updated nodes will then be
/// able to proceed past the block height of the halt.
const TOTAL_HALT_COUNT: u64 = 0;
const TOTAL_HALT_COUNT: u64 = 1;

#[async_trait]
pub trait StateReadExt: StateRead {
Expand Down
22 changes: 13 additions & 9 deletions crates/core/component/dex/src/component/dex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub trait StateReadExt: StateRead {
height: u64,
trading_pair: DirectedTradingPair,
) -> Result<Option<SwapExecution>> {
self.get(&state_key::swap_execution(height, trading_pair))
self.nonverifiable_get(state_key::swap_execution(height, trading_pair).as_bytes())
.await
}

Expand Down Expand Up @@ -240,17 +240,11 @@ pub trait StateWriteExt: StateWrite + StateReadExt {
// Store the swap executions for both directions in the state as well.
if let Some(swap_execution) = swap_execution_1_for_2.clone() {
let tp_1_for_2 = DirectedTradingPair::new(trading_pair.asset_1, trading_pair.asset_2);
self.put(
state_key::swap_execution(height, tp_1_for_2),
swap_execution,
);
self.put_swap_execution_at_height(height, tp_1_for_2, swap_execution);
}
if let Some(swap_execution) = swap_execution_2_for_1.clone() {
let tp_2_for_1 = DirectedTradingPair::new(trading_pair.asset_2, trading_pair.asset_1);
self.put(
state_key::swap_execution(height, tp_2_for_1),
swap_execution,
);
self.put_swap_execution_at_height(height, tp_2_for_1, swap_execution);
}

// ... and also add it to the set in the compact block to be pushed out to clients.
Expand Down Expand Up @@ -299,6 +293,16 @@ pub trait StateWriteExt: StateWrite + StateReadExt {

Ok(())
}

fn put_swap_execution_at_height(
&mut self,
height: u64,
pair: DirectedTradingPair,
swap_execution: SwapExecution,
) {
let path = state_key::swap_execution(height, pair);
self.nonverifiable_put(path.as_bytes().to_vec(), swap_execution);
}
}

impl<T: StateWrite> StateWriteExt for T {}
9 changes: 9 additions & 0 deletions crates/proto/src/state/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ pub trait StateWriteProto: StateWrite + Send + Sync {
self.put_raw(key, value.encode_to_vec());
}

/// Puts a domain type into the nonverifiable key-value store with the given key
fn nonverifiable_put<D>(&mut self, key: Vec<u8>, value: D)
where
D: DomainType,
anyhow::Error: From<<D as TryFrom<D::Proto>>::Error>,
{
self.nonverifiable_put_raw(key, value.encode_to_vec());
}

/// Records a Protobuf message as a typed ABCI event.
fn record_proto<E>(&mut self, proto_event: E)
where
Expand Down
Loading