From 3b0fa70aa43e94b405d0ef5e6be8d82a0d48635c Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Thu, 18 Apr 2024 14:55:50 -0700 Subject: [PATCH] fix(pd): correct swap claim height check Make swap_claim check the exact epoch and block in circuit Read epoch from state when creating batch swap output This moves the epoch read directly to the route_and_fill method. Depcrate epoch_starting_height in BSOD Store epoch and block instead of position within BatchSwapOutputDataVar Write migration Add comments about relative block conversion Fix warnings Increment total halt count Change which migration runs Drop extra references to storage before releasing it Use nonverifiable storage for compact block translation This is where they actually are --- Cargo.lock | 1 + crates/bench/benches/swap_claim.rs | 2 +- crates/bin/pcli/tests/proof.rs | 2 +- crates/bin/pd/Cargo.toml | 1 + crates/bin/pd/src/main.rs | 4 +- crates/bin/pd/src/migrate.rs | 12 +- crates/bin/pd/src/migrate/testnet72.rs | 206 ++++++++++++++++++ .../src/gen/proto_descriptor.bin.no_lfs | Bin 98383 -> 95074 bytes crates/core/app/src/app/mod.rs | 2 +- .../dex/src/batch_swap_output_data.rs | 67 ++++-- .../src/component/circuit_breaker/value.rs | 4 +- .../core/component/dex/src/component/dex.rs | 3 - .../src/component/router/route_and_fill.rs | 24 +- .../dex/src/component/router/tests.rs | 4 +- .../core/component/dex/src/component/tests.rs | 6 +- .../component/dex/src/swap_claim/proof.rs | 33 +-- .../proof-params/src/gen/swapclaim_id.rs | 4 +- .../proof-params/src/gen/swapclaim_pk.bin | 4 +- .../proof-params/src/gen/swapclaim_vk.param | Bin 2888 -> 2888 bytes .../src/gen/penumbra.core.component.dex.v1.rs | 4 + .../penumbra.core.component.dex.v1.serde.rs | 21 ++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 380322 -> 377232 bytes .../penumbra/core/component/dex/v1/dex.proto | 4 +- 23 files changed, 337 insertions(+), 71 deletions(-) create mode 100644 crates/bin/pd/src/migrate/testnet72.rs diff --git a/Cargo.lock b/Cargo.lock index c560afabc8..5a87a1f8cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4580,6 +4580,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 a3eacf3860..089cf4d2f9 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 9951853bb1..0e59c1ed6f 100644 --- a/crates/bin/pd/Cargo.toml +++ b/crates/bin/pd/Cargo.toml @@ -81,6 +81,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 e8dd635958..4d56ac4501 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -13,7 +13,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, @@ -442,7 +442,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 d68575f5e6..c8cf43320a 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..4af2b34378 --- /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_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/cnidarium/src/gen/proto_descriptor.bin.no_lfs b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs index 67d781308dc15349cc1633e0986b9be4d49fda1f..d50e1bd8818eb8b4d521b590c16703d42bad4e23 100644 GIT binary patch delta 19801 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 QsWnq;W;8wa^^V#931P9$$^ZZW diff --git a/crates/core/app/src/app/mod.rs b/crates/core/app/src/app/mod.rs index e08991de43..311368ff45 100644 --- a/crates/core/app/src/app/mod.rs +++ b/crates/core/app/src/app/mod.rs @@ -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 = 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 ac76907569..f46426b93d 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, @@ -252,7 +252,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 bd9c5f4de6..21415e426b 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 69baee6c9b..de2a7af4ae 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 4bd2b584a5dd9a63a3f92eace8fdf9fb648339ac..72be8023cd42cbce6574223f1cad11c3e74e1765 100644 GIT binary patch delta 2725 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, /// 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 b7294296fc..20ca700579 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 8a26d29b046550f594d789bdc692d546f5431d8b..6d6705ccf6ffe04d9edbb031ba95f53491d7f5fa 100644 GIT binary patch delta 29530 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
    +@0K**MBa7v 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 diff --git a/proto/penumbra/penumbra/core/component/dex/v1/dex.proto b/proto/penumbra/penumbra/core/component/dex/v1/dex.proto index 4ba3cbb394..af0baaf886 100644 --- a/proto/penumbra/penumbra/core/component/dex/v1/dex.proto +++ b/proto/penumbra/penumbra/core/component/dex/v1/dex.proto @@ -226,7 +226,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.