pd/migrate/
testnet74.rs

1//! Contains functions related to the migration script of Testnet74
2#![allow(dead_code)]
3
4use anyhow;
5use cnidarium::{EscapedByteSlice, Snapshot, StateDelta, StateRead, StateWrite, Storage};
6use futures::StreamExt as _;
7use jmt::RootHash;
8use penumbra_sdk_app::{app::StateReadExt as _, SUBSTORE_PREFIXES};
9use penumbra_sdk_auction::{params::AuctionParameters, StateWriteExt};
10use penumbra_sdk_dex::SwapExecution;
11use penumbra_sdk_num::Amount;
12use penumbra_sdk_proto::{penumbra::core::component as pb, StateReadProto, StateWriteProto};
13use penumbra_sdk_sct::component::clock::{EpochManager, EpochRead};
14use std::path::PathBuf;
15
16use crate::network::generate::NetworkConfig;
17
18/// Writes the auction parameters to the chain state.
19async fn write_auction_parameters(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
20    let params = AuctionParameters {};
21    delta.put_auction_params(params);
22    Ok(())
23}
24
25/// Updates arb execution output amounts to include the input amount instead
26/// of reporting only profit (see #3790).
27async fn fix_arb_execution_outputs(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
28    let mut stream = delta.prefix_proto("dex/arb_execution/");
29    while let Some(r) = stream.next().await {
30        let (key, swap_ex_proto): (String, pb::dex::v1::SwapExecution) = r?;
31        let mut swap_ex: SwapExecution = swap_ex_proto.try_into()?;
32        swap_ex.output = swap_ex
33            .input
34            .asset_id
35            .value(swap_ex.output.amount + swap_ex.input.amount);
36        delta.put(key, swap_ex);
37    }
38    Ok(())
39}
40
41/// Update base liquidity index values to be proto-encoded. Previously they were stored as big-endian
42/// encoded amounts, but in https://github.com/penumbra-zone/penumbra/pull/4188 they were changed
43/// to be proto-encoded.
44///
45/// This will rewrite all values under the `dex/ab/` prefix to be proto-encoded.
46async fn rewrite_base_liquidity_indices(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
47    let prefix_key = "dex/ab/".as_bytes();
48    tracing::trace!(prefix_key = ?EscapedByteSlice(&prefix_key), "updating base liquidity index values");
49    let mut liquidity_stream = delta.nonverifiable_prefix_raw(&prefix_key).boxed();
50
51    while let Some(r) = liquidity_stream.next().await {
52        let (key, raw_amount): (Vec<u8>, Vec<u8>) = r?;
53        tracing::info!(?key, raw_amount = ?EscapedByteSlice(&raw_amount), "migrating base liquidity index entry");
54
55        let amount = Amount::from_be_bytes(raw_amount.as_slice().try_into()?);
56
57        // Store the correctly formatted new value:
58        delta.nonverifiable_put(key.clone(), amount);
59        tracing::info!(
60            key = ?EscapedByteSlice(&key),
61            raw_amount = ?EscapedByteSlice(&raw_amount),
62            ?amount,
63            "updated base liquidity index"
64        );
65    }
66
67    Ok(())
68}
69
70/// Update the ordering of liquidity position indices to return in descending order (see #4189)
71///
72/// Lookups for liquidity positions based on starting asset were ordered backwards
73/// and returning the positions with the least liquidity first. This migration
74/// needs to modify the keys stored under the nonverifiable `dex/ra/` prefix key to reverse
75/// the ordering of the existing data.
76async fn update_lp_index_order(delta: &mut StateDelta<Snapshot>) -> anyhow::Result<()> {
77    let prefix_key = "dex/ra/".as_bytes();
78    tracing::trace!(prefix_key = ?EscapedByteSlice(&prefix_key), "updating liquidity position indices");
79    let mut liquidity_stream = delta.nonverifiable_prefix_raw(&prefix_key).boxed();
80
81    while let Some(r) = liquidity_stream.next().await {
82        let (old_key, asset_id): (Vec<u8>, Vec<u8>) = r?;
83        tracing::info!(?old_key, asset_id = ?EscapedByteSlice(&asset_id), "migrating asset liquidity");
84
85        // Construct the new key:
86        let mut new_key = [0u8; 55];
87        new_key[0..7].copy_from_slice(b"dex/ra/");
88        // The "from" asset ID remains the same in both keys.
89        new_key[7..32 + 7].copy_from_slice(&old_key[7..32 + 7]);
90        // Use the complement of the amount to ensure that the keys are ordered in descending order.
91        let a_from_b = Amount::from_be_bytes(old_key[32 + 7..32 + 7 + 16].try_into()?);
92        new_key[32 + 7..32 + 7 + 16].copy_from_slice(&(!a_from_b).to_be_bytes());
93
94        // Delete the old incorrectly ordered key:
95        delta.nonverifiable_delete(old_key.clone());
96
97        // Store the correctly formatted new key:
98        delta.nonverifiable_put_raw(new_key.to_vec(), asset_id);
99        tracing::info!(
100            new_key = ?EscapedByteSlice(&new_key),
101            ?old_key,
102            "updated liquidity index"
103        );
104    }
105
106    Ok(())
107}
108
109/// Run the full migration, given an export path and a start time for genesis.
110///
111/// This migration script is responsible for:
112///
113/// - Updating the ordering of liquidity position indices to return in descending order (see #4189)
114///     * nonverifiable: `dex/ra/`
115/// - Updating arb execution output amounts to include the input amount instead of reporting only profit (see #3790)
116///     * JMT: `dex/arb_execution/`
117/// - Add `AuctionParameters` to the chain state
118///     * JMT: `dex/auction_parameters`
119/// - Update the base liquidity index values to be proto-encoded (see #4188)
120///     * nonverifiable: `dex/ab/`
121pub async fn migrate(
122    storage: Storage,
123    path_to_export: PathBuf,
124    genesis_start: Option<tendermint::time::Time>,
125) -> anyhow::Result<()> {
126    // Setup:
127    let export_state = storage.latest_snapshot();
128    let root_hash = export_state.root_hash().await.expect("can get root hash");
129    let pre_upgrade_root_hash: RootHash = root_hash.into();
130    let pre_upgrade_height = export_state
131        .get_block_height()
132        .await
133        .expect("can get block height");
134    let post_upgrade_height = pre_upgrade_height.wrapping_add(1);
135
136    // We initialize a `StateDelta` and start by reaching into the JMT for all entries matching the
137    // swap execution prefix. Then, we write each entry to the nv-storage.
138    let mut delta = StateDelta::new(export_state);
139    let (migration_duration, post_upgrade_root_hash) = {
140        let start_time = std::time::SystemTime::now();
141
142        // Update LP index order.
143        update_lp_index_order(&mut delta).await?;
144
145        // Fix the arb execution output amounts.
146        fix_arb_execution_outputs(&mut delta).await?;
147
148        // Write auction parameters
149        write_auction_parameters(&mut delta).await?;
150
151        // Rewrite base liquidity indices as proto-encoded
152        rewrite_base_liquidity_indices(&mut delta).await?;
153
154        delta.put_block_height(0u64);
155        let post_upgrade_root_hash = storage.commit_in_place(delta).await?;
156        tracing::info!(?post_upgrade_root_hash, "post-upgrade root hash");
157
158        (
159            start_time.elapsed().expect("start time not set"),
160            post_upgrade_root_hash,
161        )
162    };
163
164    tracing::info!(?post_upgrade_root_hash, "post-upgrade root hash");
165    storage.release().await;
166
167    let rocksdb_dir = path_to_export.join("rocksdb");
168    let storage = Storage::load(rocksdb_dir, SUBSTORE_PREFIXES.to_vec()).await?;
169    let migrated_state = storage.latest_snapshot();
170    storage.release().await;
171
172    // The migration is complete, now we need to generate a genesis file. To do this, we need
173    // to lookup a validator view from the chain, and specify the post-upgrade app hash and
174    // initial height.
175    let chain_id = migrated_state.get_chain_id().await?;
176    let app_state = penumbra_sdk_app::genesis::Content {
177        chain_id,
178        ..Default::default()
179    };
180    let mut genesis = NetworkConfig::make_genesis(app_state.clone()).expect("can make genesis");
181    genesis.app_hash = post_upgrade_root_hash
182        .0
183        .to_vec()
184        .try_into()
185        .expect("infaillible conversion");
186    genesis.initial_height = post_upgrade_height as i64;
187    genesis.genesis_time = genesis_start.unwrap_or_else(|| {
188        let now = tendermint::time::Time::now();
189        tracing::info!(%now, "no genesis time provided, detecting a testing setup");
190        now
191    });
192    let checkpoint = post_upgrade_root_hash.0.to_vec();
193    let genesis = NetworkConfig::make_checkpoint(genesis, Some(checkpoint));
194
195    let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis");
196    tracing::info!("genesis: {}", genesis_json);
197    let genesis_path = path_to_export.join("genesis.json");
198    std::fs::write(genesis_path, genesis_json).expect("can write genesis");
199
200    let validator_state_path = path_to_export.join("priv_validator_state.json");
201    let fresh_validator_state = crate::network::generate::NetworkValidator::initial_state();
202    std::fs::write(validator_state_path, fresh_validator_state).expect("can write validator state");
203
204    tracing::info!(
205        pre_upgrade_height,
206        post_upgrade_height,
207        ?pre_upgrade_root_hash,
208        ?post_upgrade_root_hash,
209        duration = migration_duration.as_secs(),
210        "successful migration!"
211    );
212
213    Ok(())
214}