diff --git a/Cargo.lock b/Cargo.lock index e933e95564..cb8da8dc26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4549,6 +4549,7 @@ dependencies = [ "penumbra-sct", "penumbra-shielded-pool", "penumbra-stake", + "penumbra-tct", "penumbra-tendermint-proxy", "penumbra-tower-trace", "penumbra-transaction", diff --git a/crates/bench/benches/swap_claim.rs b/crates/bench/benches/swap_claim.rs index 2ba03515bb..cb1e3503ad 100644 --- a/crates/bench/benches/swap_claim.rs +++ b/crates/bench/benches/swap_claim.rs @@ -70,7 +70,7 @@ fn swap_claim_proving_time(c: &mut Criterion) { unfilled_2: Amount::from(50u64), height: height.into(), trading_pair: swap_plaintext.trading_pair, - epoch_starting_height: (epoch_duration * position.epoch()).into(), + sct_position_prefix: position, }; let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i)); diff --git a/crates/bin/pcli/tests/proof.rs b/crates/bin/pcli/tests/proof.rs index adaa09277e..e84b1b2a0d 100644 --- a/crates/bin/pcli/tests/proof.rs +++ b/crates/bin/pcli/tests/proof.rs @@ -278,7 +278,7 @@ fn swap_claim_parameters_vs_current_swap_claim_circuit() { unfilled_2: Amount::from(50u64), height: height.into(), trading_pair: swap_plaintext.trading_pair, - epoch_starting_height: (epoch_duration * position.epoch()).into(), + sct_position_prefix: position, }; let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i)); diff --git a/crates/bin/pd/Cargo.toml b/crates/bin/pd/Cargo.toml index 4447152a5c..bc53fc968c 100644 --- a/crates/bin/pd/Cargo.toml +++ b/crates/bin/pd/Cargo.toml @@ -80,6 +80,7 @@ penumbra-proto = { workspace = true, default-features = true } penumbra-sct = { workspace = true, default-features = true } penumbra-shielded-pool = { workspace = true, features = ["parallel"], default-features = true } penumbra-stake = { workspace = true, features = ["parallel"], default-features = true } +penumbra-tct = { workspace = true, default-features = true } penumbra-tendermint-proxy = { path = "../../util/tendermint-proxy" } penumbra-tower-trace = { path = "../../util/tower-trace" } penumbra-transaction = { workspace = true, default-features = true } diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index 368d4066a3..bc919c5a04 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -12,7 +12,7 @@ use cnidarium::{StateDelta, Storage}; use metrics_exporter_prometheus::PrometheusBuilder; use pd::{ cli::{Opt, RootCommand, TestnetCommand}, - migrate::Migration::Testnet70, + migrate::Migration::Testnet72, testnet::{ config::{get_testnet_dir, parse_tm_address, url_has_necessary_parts}, generate::TestnetConfig, @@ -432,7 +432,7 @@ async fn main() -> anyhow::Result<()> { migrate_archive, } => { tracing::info!("migrating state in {}", target_directory.display()); - Testnet70 + Testnet72 .migrate(target_directory.clone(), genesis_start) .await .context("failed to upgrade state")?; diff --git a/crates/bin/pd/src/migrate.rs b/crates/bin/pd/src/migrate.rs index 922d0bc001..038d67da68 100644 --- a/crates/bin/pd/src/migrate.rs +++ b/crates/bin/pd/src/migrate.rs @@ -4,6 +4,8 @@ //! node operators must coordinate to perform a chain upgrade. //! This module declares how local `pd` state should be altered, if at all, //! in order to be compatible with the network post-chain-upgrade. +mod testnet72; + use anyhow::Context; use futures::StreamExt as _; use std::path::PathBuf; @@ -28,6 +30,9 @@ pub enum Migration { SimpleMigration, /// Testnet-70 migration: move swap executions from the jmt to nv-storage. Testnet70, + /// Testnet-72 migration: + /// - Migrate `BatchSwapOutputData` to new protobuf, replacing epoch height with index. + Testnet72, } impl Migration { @@ -37,7 +42,7 @@ impl Migration { genesis_start: Option, ) -> anyhow::Result<()> { match self { - Migration::Noop => (), + Migration::Noop => Ok(()), Migration::SimpleMigration => { let rocksdb_dir = path_to_export.join("rocksdb"); let storage = Storage::load(rocksdb_dir, SUBSTORE_PREFIXES.to_vec()).await?; @@ -101,6 +106,7 @@ impl Migration { crate::testnet::generate::TestnetValidator::initial_state(); std::fs::write(validator_state_path, fresh_validator_state) .expect("can write validator state"); + Ok(()) } Migration::Testnet70 => { // Our goal is to fetch all swap executions from the jmt and store them in nv-storage. @@ -189,9 +195,11 @@ impl Migration { duration = migration_duration.as_secs(), "successful migration!" ); + + Ok(()) } + Migration::Testnet72 => testnet72::migrate(path_to_export, genesis_start).await, } - Ok(()) } } diff --git a/crates/bin/pd/src/migrate/testnet72.rs b/crates/bin/pd/src/migrate/testnet72.rs new file mode 100644 index 0000000000..e6c8f6bcd9 --- /dev/null +++ b/crates/bin/pd/src/migrate/testnet72.rs @@ -0,0 +1,206 @@ +//! Contains functions related to the migration script of Testnet72 + +use anyhow; +use cnidarium::{Snapshot, StateDelta, StateRead, StateWrite, Storage}; +use futures::StreamExt as _; +use jmt::RootHash; +use penumbra_app::app::StateReadExt as _; +use penumbra_app::SUBSTORE_PREFIXES; +use penumbra_proto::core::component::sct::v1::query_service_server::QueryService; +use penumbra_proto::penumbra::core::component as pb; +use penumbra_proto::StateWriteProto; +use penumbra_sct::component::clock::{EpochManager, EpochRead}; +use penumbra_sct::component::rpc::Server as SctServer; +use penumbra_tct::Position; +use prost::Message; +use std::path::PathBuf; +use std::sync::Arc; +use tonic::IntoRequest; + +use crate::testnet::generate::TestnetConfig; + +/// The context holding various query services we need to help perform the migration. +#[derive(Clone)] +struct Context { + sct_server: Arc, +} + +impl Context { + /// Create a new context from the state storage. + fn new(storage: Storage) -> Self { + Self { + sct_server: Arc::new(SctServer::new(storage)), + } + } + + /// Use storage to lookup the index of an epoch based on its starting heights + async fn epoch_height_to_index(&self, epoch_starting_height: u64) -> anyhow::Result { + Ok(self + .sct_server + .epoch_by_height( + pb::sct::v1::EpochByHeightRequest { + height: epoch_starting_height, + } + .into_request(), + ) + .await? + .into_inner() + .epoch + .expect(&format!( + "epoch at height {} should be present", + epoch_starting_height + )) + .index) + } + + /// Translate the protobuf for a BSOD by populating the correct data and emptying the + /// deprecated field. + #[allow(deprecated)] + async fn translate_bsod( + &self, + bsod: pb::dex::v1::BatchSwapOutputData, + ) -> anyhow::Result { + let sct_position_prefix: u64 = { + let epoch = self + .epoch_height_to_index(bsod.epoch_starting_height) + .await?; + Position::from(( + u16::try_from(epoch).expect("epoch should fit in 16 bits"), + u16::try_from(bsod.height - bsod.epoch_starting_height) + .expect("block index should fit in 16 bits"), + 0, + )) + .into() + }; + Ok(pb::dex::v1::BatchSwapOutputData { + sct_position_prefix, + epoch_starting_height: Default::default(), + ..bsod + }) + } + + async fn translate_compact_block( + &self, + compact_block: pb::compact_block::v1::CompactBlock, + ) -> anyhow::Result { + let mut swap_outputs = Vec::with_capacity(compact_block.swap_outputs.len()); + for bsod in compact_block.swap_outputs { + swap_outputs.push(self.translate_bsod(bsod).await?); + } + Ok(pb::compact_block::v1::CompactBlock { + swap_outputs, + ..compact_block + }) + } +} + +/// Translate all of the BSODs inside dex storage to the new format. +async fn translate_dex_storage( + ctx: Context, + delta: &mut StateDelta, +) -> anyhow::Result<()> { + let mut stream = delta.prefix_raw("dex/output/"); + while let Some(r) = stream.next().await { + let (key, bsod_bytes) = r?; + let bsod = pb::dex::v1::BatchSwapOutputData::decode(bsod_bytes.as_slice())?; + let bsod = ctx.translate_bsod(bsod).await?; + delta.put_proto(key, bsod); + } + Ok(()) +} + +/// Translate all of the compact block storage to hold the new BSOD data inside the compact blocks. +async fn translate_compact_block_storage( + ctx: Context, + delta: &mut StateDelta, +) -> anyhow::Result<()> { + let mut stream = delta.nonverifiable_prefix_raw("compactblock/".as_bytes()); + while let Some(r) = stream.next().await { + let (key, compactblock_bytes) = r?; + let block = pb::compact_block::v1::CompactBlock::decode(compactblock_bytes.as_slice())?; + let block = ctx.translate_compact_block(block).await?; + delta.nonverifiable_put_raw(key, block.encode_to_vec()); + } + Ok(()) +} + +/// Run the full migration, given an export path and a start time for genesis. +pub async fn migrate( + path_to_export: PathBuf, + genesis_start: Option, +) -> anyhow::Result<()> { + 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); + + let mut delta = StateDelta::new(export_state); + let (migration_duration, post_upgrade_root_hash) = { + let start_time = std::time::SystemTime::now(); + let ctx = Context::new(storage.clone()); + + // Translate inside dex storage. + translate_dex_storage(ctx.clone(), &mut delta).await?; + // Translate inside compact block storage. + translate_compact_block_storage(ctx.clone(), &mut delta).await?; + + 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"); + + (start_time.elapsed().unwrap(), post_upgrade_root_hash) + }; + + 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_app::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(()) +} diff --git a/crates/core/app/src/app/mod.rs b/crates/core/app/src/app/mod.rs index f918e1c642..150047225b 100644 --- a/crates/core/app/src/app/mod.rs +++ b/crates/core/app/src/app/mod.rs @@ -634,7 +634,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 = 1; +const TOTAL_HALT_COUNT: u64 = 2; #[async_trait] pub trait StateReadExt: StateRead { diff --git a/crates/core/component/dex/src/batch_swap_output_data.rs b/crates/core/component/dex/src/batch_swap_output_data.rs index f40a446b46..33afb327f5 100644 --- a/crates/core/component/dex/src/batch_swap_output_data.rs +++ b/crates/core/component/dex/src/batch_swap_output_data.rs @@ -8,6 +8,7 @@ use ark_r1cs_std::{ use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use decaf377::{r1cs::FqVar, Fq}; use penumbra_proto::{penumbra::core::component::dex::v1 as pb, DomainType}; +use penumbra_tct::Position; use serde::{Deserialize, Serialize}; use penumbra_num::fixpoint::{bit_constrain, U128x128, U128x128Var}; @@ -36,8 +37,8 @@ pub struct BatchSwapOutputData { pub height: u64, /// The trading pair associated with the batch swap. pub trading_pair: TradingPair, - /// The starting block height of the epoch for which the batch swap data is valid. - pub epoch_starting_height: u64, + /// The position prefix where this batch swap occurred. The commitment index must be 0. + pub sct_position_prefix: Position, } impl BatchSwapOutputData { @@ -117,19 +118,19 @@ impl ToConstraintField for BatchSwapOutputData { .expect("U128x128 types are Bls12-377 field members"), ); public_inputs.extend( - Fq::from(self.height) + self.trading_pair .to_field_elements() - .expect("Fq types are Bls12-377 field members"), + .expect("trading_pair is a Bls12-377 field member"), ); public_inputs.extend( - self.trading_pair + Fq::from(self.sct_position_prefix.epoch()) .to_field_elements() - .expect("trading_pair is a Bls12-377 field member"), + .expect("Position types are Bls12-377 field members"), ); public_inputs.extend( - Fq::from(self.epoch_starting_height) + Fq::from(self.sct_position_prefix.block()) .to_field_elements() - .expect("Fq types are Bls12-377 field members"), + .expect("Position types are Bls12-377 field members"), ); Some(public_inputs) } @@ -142,9 +143,9 @@ pub struct BatchSwapOutputDataVar { pub lambda_2: U128x128Var, pub unfilled_1: U128x128Var, pub unfilled_2: U128x128Var, - pub height: FqVar, pub trading_pair: TradingPairVar, - pub epoch_starting_height: FqVar, + pub epoch: FqVar, + pub block_within_epoch: FqVar, } impl AllocVar for BatchSwapOutputDataVar { @@ -168,18 +169,23 @@ impl AllocVar for BatchSwapOutputDataVar { let unfilled_1 = U128x128Var::new_variable(cs.clone(), || Ok(unfilled_1_fixpoint), mode)?; let unfilled_2_fixpoint: U128x128 = output_data.unfilled_2.into(); let unfilled_2 = U128x128Var::new_variable(cs.clone(), || Ok(unfilled_2_fixpoint), mode)?; - let height = FqVar::new_variable(cs.clone(), || Ok(Fq::from(output_data.height)), mode)?; - // Check the height is 64 bits - let _ = bit_constrain(height.clone(), 64); let trading_pair = TradingPairVar::new_variable_unchecked( cs.clone(), || Ok(output_data.trading_pair), mode, )?; - let epoch_starting_height = - FqVar::new_variable(cs, || Ok(Fq::from(output_data.epoch_starting_height)), mode)?; - // Check the epoch starting height is 64 bits - let _ = bit_constrain(epoch_starting_height.clone(), 64); + let epoch = FqVar::new_variable( + cs.clone(), + || Ok(Fq::from(output_data.sct_position_prefix.epoch())), + mode, + )?; + bit_constrain(epoch.clone(), 16)?; + let block_within_epoch = FqVar::new_variable( + cs.clone(), + || Ok(Fq::from(output_data.sct_position_prefix.block())), + mode, + )?; + bit_constrain(block_within_epoch.clone(), 16)?; Ok(Self { delta_1, @@ -189,8 +195,8 @@ impl AllocVar for BatchSwapOutputDataVar { unfilled_1, unfilled_2, trading_pair, - height, - epoch_starting_height, + epoch, + block_within_epoch, }) } } @@ -201,6 +207,7 @@ impl DomainType for BatchSwapOutputData { impl From for pb::BatchSwapOutputData { fn from(s: BatchSwapOutputData) -> Self { + #[allow(deprecated)] pb::BatchSwapOutputData { delta_1: Some(s.delta_1.into()), delta_2: Some(s.delta_2.into()), @@ -209,8 +216,12 @@ impl From for pb::BatchSwapOutputData { unfilled_1: Some(s.unfilled_1.into()), unfilled_2: Some(s.unfilled_2.into()), height: s.height, - epoch_starting_height: s.epoch_starting_height, trading_pair: Some(s.trading_pair.into()), + sct_position_prefix: s.sct_position_prefix.into(), + // Deprecated fields we explicitly fill with defaults. + // We could instead use a `..Default::default()` here, but that would silently + // work if we were to add fields to the domain type. + epoch_starting_height: Default::default(), } } } @@ -276,6 +287,14 @@ impl From for pb::BatchSwapOutputDataResponse { impl TryFrom for BatchSwapOutputData { type Error = anyhow::Error; fn try_from(s: pb::BatchSwapOutputData) -> Result { + let sct_position_prefix = { + let prefix = Position::from(s.sct_position_prefix); + anyhow::ensure!( + prefix.commitment() == 0, + "sct_position_prefix.commitment() != 0" + ); + prefix + }; Ok(Self { delta_1: s .delta_1 @@ -306,7 +325,7 @@ impl TryFrom for BatchSwapOutputData { .trading_pair .ok_or_else(|| anyhow!("Missing trading_pair"))? .try_into()?, - epoch_starting_height: s.epoch_starting_height, + sct_position_prefix, }) } } @@ -421,9 +440,9 @@ mod tests { lambda_2: Amount::from(1u32), unfilled_1: Amount::from(1u32), unfilled_2: Amount::from(1u32), - height: 1, + height: 0, trading_pair, - epoch_starting_height: 1, + sct_position_prefix: 0u64.into(), }, } } @@ -444,7 +463,7 @@ mod tests { unfilled_2: Amount::from(50u64), height: 0u64, trading_pair, - epoch_starting_height: 0u64, + sct_position_prefix: 0u64.into(), }; // Now suppose our user's contribution is: diff --git a/crates/core/component/dex/src/component/circuit_breaker/value.rs b/crates/core/component/dex/src/component/circuit_breaker/value.rs index bc5c06a8b1..f911aa8b12 100644 --- a/crates/core/component/dex/src/component/circuit_breaker/value.rs +++ b/crates/core/component/dex/src/component/circuit_breaker/value.rs @@ -161,7 +161,7 @@ mod tests { unfilled_2: 0u64.into(), height: 1, trading_pair: pair_1.into_directed_trading_pair().into(), - epoch_starting_height: 0, + sct_position_prefix: Default::default(), }, None, None, @@ -250,7 +250,7 @@ mod tests { let routing_params = state.routing_params().await.unwrap(); // This call should panic due to the outflow of gn not being covered by the circuit breaker. state - .handle_batch_swaps(trading_pair, swap_flow, 0, 0, routing_params) + .handle_batch_swaps(trading_pair, swap_flow, 0, routing_params) .await .expect("unable to process batch swaps"); } diff --git a/crates/core/component/dex/src/component/dex.rs b/crates/core/component/dex/src/component/dex.rs index bdb9ada0c5..0172e34c62 100644 --- a/crates/core/component/dex/src/component/dex.rs +++ b/crates/core/component/dex/src/component/dex.rs @@ -7,7 +7,6 @@ use cnidarium_component::Component; use penumbra_asset::{asset, Value, STAKING_TOKEN_ASSET_ID}; use penumbra_num::Amount; use penumbra_proto::{StateReadProto, StateWriteProto}; -use penumbra_sct::component::clock::EpochRead; use tendermint::v0_37::abci; use tracing::instrument; @@ -56,7 +55,6 @@ impl Component for Dex { // 2. For each batch swap during the block, calculate clearing prices and set in the JMT. - let current_epoch = state.get_current_epoch().await.expect("epoch is set"); let routing_params = state.routing_params().await.expect("dex params are set"); for (trading_pair, swap_flows) in state.swap_flows() { @@ -69,7 +67,6 @@ impl Component for Dex { .height .try_into() .expect("height is part of the end block data"), - current_epoch.start_height, // Always include both ends of the target pair as fixed candidates. routing_params .clone() diff --git a/crates/core/component/dex/src/component/router/route_and_fill.rs b/crates/core/component/dex/src/component/router/route_and_fill.rs index b18a786200..41445ae23c 100644 --- a/crates/core/component/dex/src/component/router/route_and_fill.rs +++ b/crates/core/component/dex/src/component/router/route_and_fill.rs @@ -5,6 +5,7 @@ use async_trait::async_trait; use cnidarium::StateWrite; use penumbra_asset::{asset, Value}; use penumbra_num::Amount; +use penumbra_sct::component::clock::EpochRead; use tracing::instrument; use crate::{ @@ -23,21 +24,13 @@ use super::fill_route::FillError; /// a block's batch swap flows. #[async_trait] pub trait HandleBatchSwaps: StateWrite + Sized { - #[instrument(skip( - self, - trading_pair, - batch_data, - block_height, - epoch_starting_height, - params - ))] + #[instrument(skip(self, trading_pair, batch_data, block_height, params))] async fn handle_batch_swaps( self: &mut Arc, trading_pair: TradingPair, batch_data: SwapFlow, - // TODO: why not read these 2 from the state? + // This will be read from the ABCI request block_height: u64, - epoch_starting_height: u64, params: RoutingParams, ) -> Result<()> where @@ -95,9 +88,9 @@ pub trait HandleBatchSwaps: StateWrite + Sized { ), None => (0u64.into(), delta_2), }; + let epoch = self.get_current_epoch().await.expect("epoch is set"); let output_data = BatchSwapOutputData { height: block_height, - epoch_starting_height, trading_pair, delta_1, delta_2, @@ -105,6 +98,15 @@ pub trait HandleBatchSwaps: StateWrite + Sized { lambda_2, unfilled_1, unfilled_2, + sct_position_prefix: ( + u16::try_from(epoch.index).expect("epoch index should be small enough"), + // The block index is determined by looking at how many blocks have elapsed since + // the start of the epoch. + u16::try_from(block_height - epoch.start_height) + .expect("block index should be small enough"), + 0, + ) + .into(), }; // Fetch the swap execution object that should have been modified during the routing and filling. diff --git a/crates/core/component/dex/src/component/router/tests.rs b/crates/core/component/dex/src/component/router/tests.rs index ccb530b738..0c26b602f6 100644 --- a/crates/core/component/dex/src/component/router/tests.rs +++ b/crates/core/component/dex/src/component/router/tests.rs @@ -1024,7 +1024,7 @@ async fn best_position_route_and_fill() -> anyhow::Result<()> { .unwrap(); let routing_params = state.routing_params().await.unwrap(); state - .handle_batch_swaps(trading_pair, swap_flow, 0u32.into(), 0, routing_params) + .handle_batch_swaps(trading_pair, swap_flow, 0u32.into(), routing_params) .await .expect("unable to process batch swaps"); @@ -1165,7 +1165,7 @@ async fn multi_hop_route_and_fill() -> anyhow::Result<()> { .unwrap(); let routing_params = state.routing_params().await.unwrap(); state - .handle_batch_swaps(trading_pair, swap_flow, 0u32.into(), 0, routing_params) + .handle_batch_swaps(trading_pair, swap_flow, 0u32.into(), routing_params) .await .expect("unable to process batch swaps"); diff --git a/crates/core/component/dex/src/component/tests.rs b/crates/core/component/dex/src/component/tests.rs index b910c0998b..cfebe53716 100644 --- a/crates/core/component/dex/src/component/tests.rs +++ b/crates/core/component/dex/src/component/tests.rs @@ -632,7 +632,7 @@ async fn swap_execution_tests() -> anyhow::Result<()> { .unwrap(); let routing_params = state.routing_params().await.unwrap(); state - .handle_batch_swaps(trading_pair, swap_flow, 0, 0, routing_params) + .handle_batch_swaps(trading_pair, swap_flow, 0, routing_params) .await .expect("unable to process batch swaps"); @@ -740,7 +740,7 @@ async fn swap_execution_tests() -> anyhow::Result<()> { .unwrap(); let routing_params = state.routing_params().await.unwrap(); state - .handle_batch_swaps(trading_pair, swap_flow, 0u32.into(), 0, routing_params) + .handle_batch_swaps(trading_pair, swap_flow, 0u32.into(), routing_params) .await .expect("unable to process batch swaps"); @@ -756,8 +756,8 @@ async fn swap_execution_tests() -> anyhow::Result<()> { unfilled_1: 0u32.into(), unfilled_2: 0u32.into(), height: 0, - epoch_starting_height: 0, trading_pair, + sct_position_prefix: Default::default(), } ); diff --git a/crates/core/component/dex/src/swap_claim/proof.rs b/crates/core/component/dex/src/swap_claim/proof.rs index c8698d131c..7d86071e65 100644 --- a/crates/core/component/dex/src/swap_claim/proof.rs +++ b/crates/core/component/dex/src/swap_claim/proof.rs @@ -123,11 +123,16 @@ fn check_satisfaction( anyhow::bail!("claim fee did not match public input"); } - let block: u64 = private.state_commitment_proof.position().block().into(); - let note_commitment_block_height: u64 = public.output_data.epoch_starting_height + block; - if note_commitment_block_height != public.output_data.height { - anyhow::bail!("swap commitment height did not match public input"); - } + anyhow::ensure!( + private.state_commitment_proof.position().block() + == public.output_data.sct_position_prefix.block(), + "scm block did not match batch swap" + ); + anyhow::ensure!( + private.state_commitment_proof.position().epoch() + == public.output_data.sct_position_prefix.epoch(), + "scm epoch did not match batch swap" + ); if private.swap_plaintext.trading_pair != public.output_data.trading_pair { anyhow::bail!("trading pair did not match public input"); @@ -255,12 +260,12 @@ impl ConstraintSynthesizer for SwapClaimCircuit { claimed_fee_var.enforce_equal(&swap_plaintext_var.claim_fee)?; // Validate the swap commitment's height matches the output data's height (i.e. the clearing price height). - let block = position_var.block()?; - let note_commitment_block_height_var = - output_data_var.epoch_starting_height.clone() + block; output_data_var - .height - .enforce_equal(¬e_commitment_block_height_var)?; + .block_within_epoch + .enforce_equal(&position_var.block()?)?; + output_data_var + .epoch + .enforce_equal(&position_var.epoch()?)?; // Validate that the output data's trading pair matches the note commitment's trading pair. output_data_var @@ -359,7 +364,7 @@ impl DummyWitness for SwapClaimCircuit { unfilled_2: Amount::from(10u64), height: 0, trading_pair: swap_plaintext.trading_pair, - epoch_starting_height: 0, + sct_position_prefix: Default::default(), }; let note_blinding_1 = Fq::from(1); let note_blinding_2 = Fq::from(1); @@ -642,7 +647,7 @@ mod tests { unfilled_2: test_bsod.unfilled_2, height: height.into(), trading_pair: swap_plaintext.trading_pair, - epoch_starting_height: (epoch_duration * position.epoch()).into(), + sct_position_prefix: Default::default(), }; let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i)); @@ -774,7 +779,7 @@ mod tests { unfilled_2: test_bsod.unfilled_2, height: height.into(), trading_pair: swap_plaintext.trading_pair, - epoch_starting_height: (epoch_duration * position.epoch()).into(), + sct_position_prefix: Default::default() }; let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i)); @@ -874,7 +879,7 @@ mod tests { unfilled_2: test_bsod.unfilled_2, height: height.into(), trading_pair: swap_plaintext.trading_pair, - epoch_starting_height: (epoch_duration * dummy_position.epoch()).into(), + sct_position_prefix: Default::default() }; let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i)); diff --git a/crates/crypto/proof-params/src/gen/swapclaim_id.rs b/crates/crypto/proof-params/src/gen/swapclaim_id.rs index 0293098666..e90d28aa7c 100644 --- a/crates/crypto/proof-params/src/gen/swapclaim_id.rs +++ b/crates/crypto/proof-params/src/gen/swapclaim_id.rs @@ -1,3 +1,3 @@ -pub const PROVING_KEY_ID: &'static str = "groth16pk1vs60etmlvwfzmn2ve0ljz0vfkzjlrhjpue5svm5ry6l076qukjcsw566rp"; -pub const VERIFICATION_KEY_ID: &'static str = "groth16vk18qjn0kxmypk8gmfc6zhjukhyxk0agmunfnhpxmf3yxq266q6sgaqwe94rc"; +pub const PROVING_KEY_ID: &'static str = "groth16pk1pfpj2hullzpeqzzyfqw85q03zz8mthht07zd3vkc562lfe776xgsvu3mfy"; +pub const VERIFICATION_KEY_ID: &'static str = "groth16vk1qyhwaxh5kq6lk2tm6fnxctynqqf7vt5j64u92zm8d8pndy7yap4qsyw855"; diff --git a/crates/crypto/proof-params/src/gen/swapclaim_pk.bin b/crates/crypto/proof-params/src/gen/swapclaim_pk.bin index 96b1d164b6..a401b19bc9 100644 --- a/crates/crypto/proof-params/src/gen/swapclaim_pk.bin +++ b/crates/crypto/proof-params/src/gen/swapclaim_pk.bin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8501f1dad9ac85d80c6421b17b838f8c6478431babfa836d3d33b5398fa6b6ad -size 26003952 +oid sha256:1190707f9815bf0135169547b888716d6731cdbe1bc4ea2fbd22655a03fe56cd +size 25957872 diff --git a/crates/crypto/proof-params/src/gen/swapclaim_vk.param b/crates/crypto/proof-params/src/gen/swapclaim_vk.param index 4bd2b584a5..72be8023cd 100644 Binary files a/crates/crypto/proof-params/src/gen/swapclaim_vk.param and b/crates/crypto/proof-params/src/gen/swapclaim_vk.param differ diff --git a/crates/proto/src/gen/penumbra.core.component.dex.v1.rs b/crates/proto/src/gen/penumbra.core.component.dex.v1.rs index 644a2c7922..503e87f82f 100644 --- a/crates/proto/src/gen/penumbra.core.component.dex.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.dex.v1.rs @@ -508,8 +508,12 @@ pub struct BatchSwapOutputData { #[prost(message, optional, tag = "8")] pub trading_pair: ::core::option::Option, /// The starting block height of the epoch for which the batch swap data is valid. + #[deprecated] #[prost(uint64, tag = "9")] pub epoch_starting_height: u64, + /// The prefix (epoch, block) of the position where this batch swap occurred. + #[prost(uint64, tag = "10")] + pub sct_position_prefix: u64, } impl ::prost::Name for BatchSwapOutputData { const NAME: &'static str = "BatchSwapOutputData"; diff --git a/crates/proto/src/gen/penumbra.core.component.dex.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.dex.v1.serde.rs index b6efda7985..4dd79b0281 100644 --- a/crates/proto/src/gen/penumbra.core.component.dex.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.dex.v1.serde.rs @@ -614,6 +614,9 @@ impl serde::Serialize for BatchSwapOutputData { if self.epoch_starting_height != 0 { len += 1; } + if self.sct_position_prefix != 0 { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.dex.v1.BatchSwapOutputData", len)?; if let Some(v) = self.delta_1.as_ref() { struct_ser.serialize_field("delta1", v)?; @@ -644,6 +647,10 @@ impl serde::Serialize for BatchSwapOutputData { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("epochStartingHeight", ToString::to_string(&self.epoch_starting_height).as_str())?; } + if self.sct_position_prefix != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sctPositionPrefix", ToString::to_string(&self.sct_position_prefix).as_str())?; + } struct_ser.end() } } @@ -671,6 +678,8 @@ impl<'de> serde::Deserialize<'de> for BatchSwapOutputData { "tradingPair", "epoch_starting_height", "epochStartingHeight", + "sct_position_prefix", + "sctPositionPrefix", ]; #[allow(clippy::enum_variant_names)] @@ -684,6 +693,7 @@ impl<'de> serde::Deserialize<'de> for BatchSwapOutputData { Height, TradingPair, EpochStartingHeight, + SctPositionPrefix, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -715,6 +725,7 @@ impl<'de> serde::Deserialize<'de> for BatchSwapOutputData { "height" => Ok(GeneratedField::Height), "tradingPair" | "trading_pair" => Ok(GeneratedField::TradingPair), "epochStartingHeight" | "epoch_starting_height" => Ok(GeneratedField::EpochStartingHeight), + "sctPositionPrefix" | "sct_position_prefix" => Ok(GeneratedField::SctPositionPrefix), _ => Ok(GeneratedField::__SkipField__), } } @@ -743,6 +754,7 @@ impl<'de> serde::Deserialize<'de> for BatchSwapOutputData { let mut height__ = None; let mut trading_pair__ = None; let mut epoch_starting_height__ = None; + let mut sct_position_prefix__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Delta1 => { @@ -803,6 +815,14 @@ impl<'de> serde::Deserialize<'de> for BatchSwapOutputData { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::SctPositionPrefix => { + if sct_position_prefix__.is_some() { + return Err(serde::de::Error::duplicate_field("sctPositionPrefix")); + } + sct_position_prefix__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -818,6 +838,7 @@ impl<'de> serde::Deserialize<'de> for BatchSwapOutputData { height: height__.unwrap_or_default(), trading_pair: trading_pair__, epoch_starting_height: epoch_starting_height__.unwrap_or_default(), + sct_position_prefix: sct_position_prefix__.unwrap_or_default(), }) } } diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 1614cc7fbd..a79c879bc8 100644 Binary files a/crates/proto/src/gen/proto_descriptor.bin.no_lfs and b/crates/proto/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/proto/penumbra/penumbra/core/component/dex/v1/dex.proto b/proto/penumbra/penumbra/core/component/dex/v1/dex.proto index 53fd9d9167..c72588e285 100644 --- a/proto/penumbra/penumbra/core/component/dex/v1/dex.proto +++ b/proto/penumbra/penumbra/core/component/dex/v1/dex.proto @@ -248,7 +248,9 @@ message BatchSwapOutputData { // The trading pair associated with the batch swap. TradingPair trading_pair = 8; // The starting block height of the epoch for which the batch swap data is valid. - uint64 epoch_starting_height = 9; + uint64 epoch_starting_height = 9 [deprecated = true]; + // The prefix (epoch, block) of the position where this batch swap occurred. + uint64 sct_position_prefix = 10; } // The trading function for a specific pair.