From 6913c4a7251dddfac3150709dcc67221c6387fb6 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Thu, 9 May 2024 21:11:39 -0700 Subject: [PATCH 01/16] update rust deps to v0.74 --- packages/wasm/crate/Cargo.toml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/wasm/crate/Cargo.toml b/packages/wasm/crate/Cargo.toml index c9b3a6ad08..0b50f4e75f 100644 --- a/packages/wasm/crate/Cargo.toml +++ b/packages/wasm/crate/Cargo.toml @@ -1,4 +1,6 @@ + + [package] name = "penumbra-wasm" version = "2.0.0" @@ -17,20 +19,20 @@ mock-database = [] [dependencies] # TODO: Use `tag` instead of `rev` once auctions land in a tagged release of # core. -penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-auction", default-features = false } -penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-asset" } -penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-compact-block", default-features = false } -penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-dex", default-features = false } -penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-fee", default-features = false } -penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-keys" } -penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-num" } -penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-proof-params", default-features = false } -penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-proto", default-features = false } -penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-sct", default-features = false } -penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-shielded-pool", default-features = false } -penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-stake", default-features = false } -penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-tct" } -penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "a500f2c72", package = "penumbra-transaction", default-features = false } +penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-auction", default-features = false } +penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-asset" } +penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-compact-block", default-features = false } +penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-dex", default-features = false } +penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-fee", default-features = false } +penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-keys" } +penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-num" } +penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-proof-params", default-features = false } +penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-proto", default-features = false } +penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-sct", default-features = false } +penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-shielded-pool", default-features = false } +penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-stake", default-features = false } +penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-tct" } +penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-transaction", default-features = false } anyhow = "1.0.80" ark-ff = { version = "0.4.2", features = ["std"] } @@ -52,3 +54,4 @@ web-sys = { version = "0.3.69", features = ["console"] } [dev-dependencies] wasm-bindgen-test = "0.3.42" serde_json = "1.0.114" + From 865f41f2d4f061b2609361b69bbd482596fbcfe5 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Thu, 9 May 2024 21:12:04 -0700 Subject: [PATCH 02/16] initial planner port --- packages/wasm/crate/Cargo.lock | 89 ++++---- packages/wasm/crate/src/planner.rs | 355 ++++++++++++++++++++--------- 2 files changed, 296 insertions(+), 148 deletions(-) diff --git a/packages/wasm/crate/Cargo.lock b/packages/wasm/crate/Cargo.lock index 61b7ba2ddd..e02184fdc8 100644 --- a/packages/wasm/crate/Cargo.lock +++ b/packages/wasm/crate/Cargo.lock @@ -776,8 +776,8 @@ dependencies = [ [[package]] name = "decaf377-fmd" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "ark-ff", "ark-serialize", @@ -790,8 +790,8 @@ dependencies = [ [[package]] name = "decaf377-ka" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "ark-ff", "decaf377 0.5.0", @@ -2087,8 +2087,8 @@ dependencies = [ [[package]] name = "penumbra-asset" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2125,8 +2125,8 @@ dependencies = [ [[package]] name = "penumbra-auction" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2171,8 +2171,8 @@ dependencies = [ [[package]] name = "penumbra-community-pool" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2201,8 +2201,8 @@ dependencies = [ [[package]] name = "penumbra-compact-block" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2233,8 +2233,8 @@ dependencies = [ [[package]] name = "penumbra-dex" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2285,8 +2285,8 @@ dependencies = [ [[package]] name = "penumbra-distributions" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "async-trait", @@ -2301,8 +2301,8 @@ dependencies = [ [[package]] name = "penumbra-fee" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2324,8 +2324,8 @@ dependencies = [ [[package]] name = "penumbra-funding" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "async-trait", @@ -2343,8 +2343,8 @@ dependencies = [ [[package]] name = "penumbra-governance" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2388,6 +2388,7 @@ dependencies = [ "rand_core", "regex", "serde", + "serde_json", "tap", "tendermint", "thiserror", @@ -2396,8 +2397,8 @@ dependencies = [ [[package]] name = "penumbra-ibc" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2429,8 +2430,8 @@ dependencies = [ [[package]] name = "penumbra-keys" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "aes", "anyhow", @@ -2473,8 +2474,8 @@ dependencies = [ [[package]] name = "penumbra-num" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2509,8 +2510,8 @@ dependencies = [ [[package]] name = "penumbra-proof-params" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ec", @@ -2535,8 +2536,8 @@ dependencies = [ [[package]] name = "penumbra-proto" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "async-trait", @@ -2562,8 +2563,8 @@ dependencies = [ [[package]] name = "penumbra-sct" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2593,8 +2594,8 @@ dependencies = [ [[package]] name = "penumbra-shielded-pool" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2641,8 +2642,8 @@ dependencies = [ [[package]] name = "penumbra-stake" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2683,8 +2684,8 @@ dependencies = [ [[package]] name = "penumbra-tct" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "ark-ed-on-bls12-377", "ark-ff", @@ -2711,8 +2712,8 @@ dependencies = [ [[package]] name = "penumbra-transaction" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "ark-ff", @@ -2762,8 +2763,8 @@ dependencies = [ [[package]] name = "penumbra-txhash" -version = "0.74.0-alpha.1" -source = "git+https://github.com/penumbra-zone/penumbra.git?rev=a500f2c72#a500f2c72206e805a85c9aa7e4b0381901ff6896" +version = "0.74.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" dependencies = [ "anyhow", "blake2b_simd 1.0.2", diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 84fcca8693..6a2ce14912 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; +use std::mem; use anyhow::anyhow; +use anyhow::Result; use ark_ff::UniformRand; use decaf377::{Fq, Fr}; use penumbra_asset::asset::{Id, Metadata}; @@ -27,12 +29,12 @@ use penumbra_proto::DomainType; use penumbra_sct::params::SctParameters; use penumbra_shielded_pool::{fmd, OutputPlan, SpendPlan}; use penumbra_stake::rate::RateData; -use penumbra_stake::{IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; +use penumbra_stake::{Delegate, IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; use penumbra_transaction::gas::GasCost; use penumbra_transaction::memo::MemoPlaintext; use penumbra_transaction::{plan::MemoPlan, ActionPlan, TransactionParameters, TransactionPlan}; use prost::Message; -use rand_core::{OsRng, RngCore}; +use rand_core::{CryptoRng, OsRng, RngCore}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -42,40 +44,99 @@ use crate::storage::IndexedDBStorage; use crate::utils; use crate::{error::WasmResult, swap_record::SwapRecord}; -struct ActionList { +/// A list of planned actions to be turned into a TransactionPlan. +/// +/// A transaction is a bundle of actions plus auxiliary data (like a memo). A +/// transaction plan is a bundle of action plans plus plans for the auxiliary +/// data (like a memo plan). The [`ActionList`] is just the list of actions, +/// which is useful for building up a [`TransactionPlan`]. +#[derive(Debug, Default, Clone)] +pub struct ActionList { // A list of the user-specified outputs. actions: Vec, // These are tracked separately for convenience when adjusting change. change_outputs: BTreeMap, + // The fee is tracked as part of the ActionList so it can be adjusted + // internally to handle special cases like swap claims. + fee: Fee, } impl ActionList { - fn new() -> Self { - Self { - actions: Vec::new(), - change_outputs: BTreeMap::new(), - } + /// Returns true if the resulting transaction would require a memo. + pub fn requires_memo(&self) -> bool { + let has_change_outputs = !self.change_outputs.is_empty(); + let has_other_outputs = self + .actions + .iter() + .any(|action| matches!(action, ActionPlan::Output(_))); + + has_change_outputs || has_other_outputs } - fn balance(&self) -> Balance { - let mut balance = Balance::zero(); - for action in &self.actions { - balance += action.balance(); - } - for action in self.change_outputs.values() { - balance += action.balance(); - } - balance + /// Convert this list of actions into a [`TransactionPlan`]. + pub fn into_plan( + self, + rng: R, + fmd_params: &fmd::Parameters, + mut transaction_parameters: TransactionParameters, + memo_plan: Option, + ) -> Result { + transaction_parameters.fee = self.fee; + + let mut plan = TransactionPlan { + actions: self + .actions + .into_iter() + .chain(self.change_outputs.into_values().map(Into::into)) + .collect(), + transaction_parameters, + memo: memo_plan, + detection_data: None, + }; + plan.populate_detection_data(rng, fmd_params.precision_bits.into()); + + // Implement a canonical ordering to the actions within the transaction + // plan to reduce client distinguishability. + plan.sort_actions(); + + Ok(plan) } - fn push(&mut self, action: ActionPlan) { - self.actions.push(action); + /// Push a new action onto this list. + pub fn push>(&mut self, action: A) { + let plan = action.into(); + + // Special case: if the plan is a `SwapClaimPlan`, adjust the fee to include the + // prepaid fee contributed by the swap claim. This helps ensure that the value + // released by the swap claim is used to pay the fee, rather than generating change. + if let ActionPlan::SwapClaim(claim) = &plan { + let claim_fee = claim.swap_plaintext.claim_fee; + if self.fee.amount() == Amount::zero() { + // If the fee is currently zero, set it to the claim fee, + // regardless of fee token, i.e., set the fee token to match + // the swap claim. + self.fee = claim_fee; + } else if self.fee.asset_matches(&claim_fee) { + // Otherwise, if the fee token matches, accumulate the amount + // released by the swap claim into the fee, rather than letting it + // be handled as change. + self.fee.0.amount += claim_fee.amount(); + } else { + // In this situation, the fee has been manually set to a + // different token than was released by the swap claim. So we + // can't accumulate the swap claim fee into it, and it will + // produce change instead. + } + } + + self.actions.push(plan); } - fn gas_estimate(&self) -> Gas { - // TODO: this won't include the gas cost for the bytes of the tx itself - // so this gas estimate will be an underestimate, but since the tx-bytes contribution - // to the fee is ideally small, hopefully it doesn't matter. + /// Compute the gas used by a transaction comprised of the actions in this list. + /// + /// Because Penumbra transactions have static gas costs, and gas use is linear in the actions, + /// this is an exact computation. + fn gas_cost(&self) -> Gas { let mut gas = Gas::zero(); for action in &self.actions { // TODO missing AddAssign @@ -90,30 +151,114 @@ impl ActionList { gas } - fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { - let base_fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + /// Use the provided gas prices and fee tier to estimate the fee for + /// the transaction. + /// + /// While the gas cost can be computed exactly, the base fee can only be + /// estimated, because the actual base fee paid by the transaction will + /// depend on the gas prices at the time it's accepted on-chain. + fn compute_fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { + let base_fee = gas_prices.fee(&self.gas_cost()); base_fee.apply_tier(*fee_tier) } - fn balance_with_fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Balance { - self.balance() - self.fee_estimate(gas_prices, fee_tier).0 + /// Use the provided gas prices and fee tier to refresh the fee estimate for + /// the transaction. + /// + /// If the current fee estimate is too low, it will be increased. In that + /// case, change notes will be adjusted to cover the increase if possible. + pub fn refresh_fee_and_change( + &mut self, + rng: R, + gas_prices: &GasPrices, + fee_tier: &FeeTier, + change_address: &Address, + ) { + // First, refresh the change outputs, to capture any surplus imbalance. + self.refresh_change(rng, &change_address); + + // Next, recompute the fee estimate for the actions and change outputs. + let new_fee = self.compute_fee_estimate(gas_prices, fee_tier); + + // Update the targeted fee with the new estimate. + if new_fee.asset_matches(&self.fee) { + // Take the max of the current fee and the new estimate. This ensures + // that if we already overpaid the fee for some reason, we don't lower it + // and cause the creation of unwanted change outputs. + self.fee.0.amount = std::cmp::max(self.fee.amount(), new_fee.amount()); + } else { + // Otherwise, overwrite the previous fee with the new estimate. + self.fee = new_fee; + } + + // Finally, adjust the change outputs to cover the fee increase if possible. + self.adjust_change_for_imbalance(); } - fn refresh_change(&mut self, change_address: Address) { + /// Return the balance of the actions in the list, without accounting for fees. + pub fn balance_without_fee(&self) -> Balance { + let mut balance = Balance::zero(); + + for action in &self.actions { + balance += action.balance(); + } + for action in self.change_outputs.values() { + balance += action.balance(); + } + + balance + } + + /// Return the balance of the actions in the list, minus the currently estimated fee + /// required to pay their gas costs. + pub fn balance_with_fee(&self) -> Balance { + self.balance_without_fee() - self.fee.0 + } + + /// Refresh the change notes used to store any surplus imbalance from the + /// actions in the list. + fn refresh_change(&mut self, mut rng: R, change_address: &Address) { self.change_outputs = BTreeMap::new(); // For each "provided" balance component, create a change note. - for value in self.balance().provided() { + for value in self.balance_with_fee().provided() { self.change_outputs.insert( value.asset_id, - OutputPlan::new(&mut OsRng, value, change_address.clone()), + OutputPlan::new(&mut rng, value, change_address.clone()), ); } } - fn adjust_change_for_fee(&mut self, fee: Fee) { - self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { - e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); - }); + /// Attempt adjust existing change notes to repair imbalance: + /// + /// - cover required balance by decreasing change if possible + /// - cover surplus balance by increasing change if possible + fn adjust_change_for_imbalance(&mut self) { + // We need to grab the current balance upfront before doing modifications. + let balance = self.balance_with_fee(); + + // Sweep surplus balance into existing change notes. + for provided in balance.provided() { + self.change_outputs + .entry(provided.asset_id) + .and_modify(|e| { + e.value.amount += provided.amount; + }); + } + + // Attempt to cover imbalance via existing change notes. + for required in balance.required() { + self.change_outputs + .entry(required.asset_id) + .and_modify(|e| { + // It's important to use saturating_sub here because + // our expectation is that we commonly won't have enough balance. + e.value.amount = e.value.amount.saturating_sub(&required.amount); + }); + } + + // Remove any 0-value change notes we might have created. + self.change_outputs + .retain(|_, output| output.value.amount > Amount::zero()); } } @@ -212,9 +357,9 @@ pub async fn plan_transaction( gas_prices.try_into()? }; - // Phase 1: process all of the user-supplied intents into complete action plans. + let mut actions = ActionList::default(); - let mut actions = ActionList::new(); + // Phase 1: process all of the user-supplied intents into complete action plans. for tpr::Output { value, address } in request.outputs { let value = value @@ -224,7 +369,7 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("missing address in output"))? .try_into()?; - let output = OutputPlan::new(&mut OsRng, value, address).into(); + let output: OutputPlan = OutputPlan::new(&mut OsRng, value, address).into(); actions.push(output); } @@ -278,7 +423,7 @@ pub async fn plan_transaction( claim_address, ); - let swap = SwapPlan::new(&mut OsRng, swap_plaintext).into(); + let swap: SwapPlan = SwapPlan::new(&mut OsRng, swap_plaintext).into(); actions.push(swap); } @@ -293,7 +438,7 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("Swap record not found"))? .try_into()?; - let swap_claim = SwapClaimPlan { + let swap_claim: SwapClaimPlan = SwapClaimPlan { swap_plaintext: swap_record.swap, position: swap_record.position, output_data: swap_record.output_data, @@ -314,7 +459,9 @@ pub async fn plan_transaction( let rate_data: RateData = rate_data .ok_or_else(|| anyhow!("missing rate data in delegation"))? .try_into()?; - actions.push(rate_data.build_delegate(epoch.into(), amount).into()); + + let delegate: Delegate = rate_data.build_delegate(epoch.into(), amount).into(); + actions.push(delegate); } for tpr::Undelegate { value, rate_data } in request.undelegations { @@ -326,11 +473,13 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("missing rate data in undelegation"))? .try_into()?; - let undelegate = rate_data.build_undelegate(epoch.into(), value.amount); + let undelegate: Undelegate = rate_data + .build_undelegate(epoch.into(), value.amount) + .into(); save_unbonding_token_metadata_if_needed(&undelegate, &storage).await?; - actions.push(undelegate.into()); + actions.push(undelegate); } for tpr::UndelegateClaim { @@ -422,7 +571,7 @@ pub async fn plan_transaction( }; save_auction_nft_metadata_if_needed( - &description, + description.id(), &storage, // When scheduling a Dutch auction, the sequence number is always 0 0, @@ -439,14 +588,26 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("missing auction ID in Dutch auction end action"))? .try_into()?; + save_auction_nft_metadata_if_needed( + auction_id, &storage, + // When ending a Dutch auction, the sequence number is always 1 + 1, + ) + .await?; + actions.push(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { auction_id, })); } - // TODO: Handle Dutch auction withdraws + // TODO (Jesse): Handle Dutch auction withdraws - // Phase 2: fill in the required spends to make the transaction balance. + // Phase 2: balance the transaction with information from the view service. + // + // It's possible that adding spends could increase the gas, increasing + // the fee amount, and so on, so we add spends iteratively. However, we + // need to query all the notes we'll use for planning upfront, so we + // don't accidentally try to use the same one twice. let fee_tier = match request.fee_mode { None => FeeTier::default(), @@ -456,14 +617,11 @@ pub async fn plan_transaction( } }; - // It's possible that adding spends could increase the gas, increasing the fee - // amount, and so on, so we add spends iteratively. + // Compute an initial fee estimate based on the actions we have so far. + actions.refresh_fee_and_change(&mut OsRng, &gas_prices, &fee_tier, &change_address); let mut notes_by_asset_id = BTreeMap::new(); - for required in actions - .balance_with_fee_estimate(&gas_prices, &fee_tier) - .required() - { + for required in actions.balance_with_fee().required() { // Find all the notes of this asset in the source account. let records = storage .get_notes(NotesRequest { @@ -481,67 +639,55 @@ pub async fn plan_transaction( let mut iterations = 0usize; - while let Some(required) = actions - .balance_with_fee_estimate(&gas_prices, &fee_tier) - .required() - .next() - { - // Spend a single note towards the required balance, if possible. - let Some(note) = notes_by_asset_id + // Now iterate over the action list's imbalances to balance the transaction. + while let Some(required) = actions.balance_with_fee().required().next() { + // Find a single note to spend towards the required balance. + let note = notes_by_asset_id .get_mut(&required.asset_id) - .expect("we already queried") + .expect("we already made a notesrequest for each required asset") .pop() - else { - return Err(anyhow!( - "ran out of notes to spend while planning transaction, need {} of asset {}", - required.amount, - required.asset_id, - ) - .into()); - }; - actions.push(SpendPlan::new(&mut OsRng, note.note, note.position).into()); + .ok_or_else(|| { + anyhow!( + "ran out of notes to spend while planning transaction, need {} of asset {}", + required.amount, + required.asset_id, + ) + })?; - // Recompute the change outputs, without accounting for fees. - actions.refresh_change(change_address.clone()); - // Now re-estimate the fee of the updated transaction and adjust the change if possible. - let fee = actions.fee_estimate(&gas_prices, &fee_tier); - actions.adjust_change_for_fee(fee); + // Add a spend for that note to the action list. + actions.push(SpendPlan::new(&mut OsRng, note.note, note.position)); - iterations += 1; + // Refresh the fee estimate and change outputs. + actions.refresh_fee_and_change(&mut OsRng, &gas_prices, &fee_tier, &change_address); + + iterations = iterations + 1; if iterations > 100 { return Err(anyhow!("failed to plan transaction after 100 iterations").into()); } } - let fee = actions.fee_estimate(&gas_prices, &fee_tier); - - let mut plan = TransactionPlan { - actions: actions - .actions - .into_iter() - .chain(actions.change_outputs.into_values().map(Into::into)) - .collect(), - transaction_parameters: TransactionParameters { - expiry_height: request.expiry_height, - chain_id, - fee, - }, - detection_data: None, - memo: None, - }; - + let mut memo = None; if let Some(pb_memo_plaintext) = request.memo { - plan.memo = Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)?); - } else if plan.output_plans().next().is_some() { + memo = Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)); + } else { // If a memo was not provided, but is required (because we have outputs), // auto-create one with the change address. - plan.memo = Some(MemoPlan::new( + memo = Some(MemoPlan::new( &mut OsRng, MemoPlaintext::new(change_address, String::new())?, - )?); + )); } - plan.populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); + let fee = actions.compute_fee_estimate(&gas_prices, &fee_tier); + + let transaction_parameters = TransactionParameters { + expiry_height: request.expiry_height, + chain_id, + fee, + }; + + let plan = + mem::take(&mut actions).into_plan(&mut OsRng, &fmd_params, transaction_parameters, memo)?; Ok(serde_wasm_bindgen::to_value(&plan)?) } @@ -561,18 +707,19 @@ async fn save_unbonding_token_metadata_if_needed( save_metadata_if_needed(metadata, storage).await } -/// When planning a Dutch auction schedule action, there will not be metadata -/// yet in the IndexedDB database for the auction NFT that the transaction will -/// output. That's because auction NFTs are derived from the auction description -/// parameters, which include a nonce. So we'll generate the metadata here and -/// save it to the database, so that the action renders correctly in the -/// transaction approval dialog. +/// When planning Dutch auction-related actions, there will not be metadata yet +/// in the IndexedDB database for the auction NFT that the transaction will +/// output. That's because auction NFTs are derived from information about the +/// auction (for example, an NFT corresponding to a newly started auction is +/// dervived from the auction description parameters, which include a nonce). So +/// we'll generate the metadata here and save it to the database, so that the +/// action renders correctly in the transaction approval dialog. async fn save_auction_nft_metadata_if_needed( - description: &DutchAuctionDescription, + id: AuctionId, storage: &IndexedDBStorage, seq: u64, ) -> WasmResult<()> { - let nft = AuctionNft::new(description.id(), seq); + let nft = AuctionNft::new(id, seq); let metadata = nft.metadata; save_metadata_if_needed(metadata, storage).await From e62572cc6b71b6cff7408bf383760c9283f2b0c7 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 10 May 2024 08:34:24 -0700 Subject: [PATCH 03/16] modified to use same ActionList impl --- packages/wasm/crate/src/planner.rs | 346 +++++------------------------ packages/wasm/crate/tests/build.rs | 5 +- 2 files changed, 63 insertions(+), 288 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 6a2ce14912..ce6efe48e2 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -1,12 +1,13 @@ -use std::collections::BTreeMap; -use std::mem; - +use crate::metadata::customize_symbol_inner; +use crate::note_record::SpendableNoteRecord; +use crate::storage::IndexedDBStorage; +use crate::utils; +use crate::{error::WasmResult, swap_record::SwapRecord}; use anyhow::anyhow; -use anyhow::Result; use ark_ff::UniformRand; use decaf377::{Fq, Fr}; use penumbra_asset::asset::{Id, Metadata}; -use penumbra_asset::{asset, Balance, Value}; +use penumbra_asset::Value; use penumbra_auction::auction::dutch::{ ActionDutchAuctionEnd, ActionDutchAuctionSchedule, DutchAuctionDescription, }; @@ -16,9 +17,9 @@ use penumbra_dex::{ swap::{SwapPlaintext, SwapPlan}, TradingPair, }; -use penumbra_fee::{Fee, FeeTier, Gas, GasPrices}; +use penumbra_fee::{FeeTier, GasPrices}; use penumbra_keys::keys::AddressIndex; -use penumbra_keys::{Address, FullViewingKey}; +use penumbra_keys::FullViewingKey; use penumbra_num::Amount; use penumbra_proto::core::app::v1::AppParameters; use penumbra_proto::core::component::ibc; @@ -30,238 +31,16 @@ use penumbra_sct::params::SctParameters; use penumbra_shielded_pool::{fmd, OutputPlan, SpendPlan}; use penumbra_stake::rate::RateData; use penumbra_stake::{Delegate, IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; -use penumbra_transaction::gas::GasCost; use penumbra_transaction::memo::MemoPlaintext; -use penumbra_transaction::{plan::MemoPlan, ActionPlan, TransactionParameters, TransactionPlan}; +use penumbra_transaction::ActionList; +use penumbra_transaction::{plan::MemoPlan, ActionPlan, TransactionParameters}; use prost::Message; -use rand_core::{CryptoRng, OsRng, RngCore}; +use rand_core::{OsRng, RngCore}; +use std::collections::BTreeMap; +use std::mem; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; -use crate::metadata::customize_symbol_inner; -use crate::note_record::SpendableNoteRecord; -use crate::storage::IndexedDBStorage; -use crate::utils; -use crate::{error::WasmResult, swap_record::SwapRecord}; - -/// A list of planned actions to be turned into a TransactionPlan. -/// -/// A transaction is a bundle of actions plus auxiliary data (like a memo). A -/// transaction plan is a bundle of action plans plus plans for the auxiliary -/// data (like a memo plan). The [`ActionList`] is just the list of actions, -/// which is useful for building up a [`TransactionPlan`]. -#[derive(Debug, Default, Clone)] -pub struct ActionList { - // A list of the user-specified outputs. - actions: Vec, - // These are tracked separately for convenience when adjusting change. - change_outputs: BTreeMap, - // The fee is tracked as part of the ActionList so it can be adjusted - // internally to handle special cases like swap claims. - fee: Fee, -} - -impl ActionList { - /// Returns true if the resulting transaction would require a memo. - pub fn requires_memo(&self) -> bool { - let has_change_outputs = !self.change_outputs.is_empty(); - let has_other_outputs = self - .actions - .iter() - .any(|action| matches!(action, ActionPlan::Output(_))); - - has_change_outputs || has_other_outputs - } - - /// Convert this list of actions into a [`TransactionPlan`]. - pub fn into_plan( - self, - rng: R, - fmd_params: &fmd::Parameters, - mut transaction_parameters: TransactionParameters, - memo_plan: Option, - ) -> Result { - transaction_parameters.fee = self.fee; - - let mut plan = TransactionPlan { - actions: self - .actions - .into_iter() - .chain(self.change_outputs.into_values().map(Into::into)) - .collect(), - transaction_parameters, - memo: memo_plan, - detection_data: None, - }; - plan.populate_detection_data(rng, fmd_params.precision_bits.into()); - - // Implement a canonical ordering to the actions within the transaction - // plan to reduce client distinguishability. - plan.sort_actions(); - - Ok(plan) - } - - /// Push a new action onto this list. - pub fn push>(&mut self, action: A) { - let plan = action.into(); - - // Special case: if the plan is a `SwapClaimPlan`, adjust the fee to include the - // prepaid fee contributed by the swap claim. This helps ensure that the value - // released by the swap claim is used to pay the fee, rather than generating change. - if let ActionPlan::SwapClaim(claim) = &plan { - let claim_fee = claim.swap_plaintext.claim_fee; - if self.fee.amount() == Amount::zero() { - // If the fee is currently zero, set it to the claim fee, - // regardless of fee token, i.e., set the fee token to match - // the swap claim. - self.fee = claim_fee; - } else if self.fee.asset_matches(&claim_fee) { - // Otherwise, if the fee token matches, accumulate the amount - // released by the swap claim into the fee, rather than letting it - // be handled as change. - self.fee.0.amount += claim_fee.amount(); - } else { - // In this situation, the fee has been manually set to a - // different token than was released by the swap claim. So we - // can't accumulate the swap claim fee into it, and it will - // produce change instead. - } - } - - self.actions.push(plan); - } - - /// Compute the gas used by a transaction comprised of the actions in this list. - /// - /// Because Penumbra transactions have static gas costs, and gas use is linear in the actions, - /// this is an exact computation. - fn gas_cost(&self) -> Gas { - let mut gas = Gas::zero(); - for action in &self.actions { - // TODO missing AddAssign - gas = gas + action.gas_cost(); - } - for action in self.change_outputs.values() { - // TODO missing AddAssign - // TODO missing GasCost impl on OutputPlan - gas = gas + ActionPlan::from(action.clone()).gas_cost(); - } - - gas - } - - /// Use the provided gas prices and fee tier to estimate the fee for - /// the transaction. - /// - /// While the gas cost can be computed exactly, the base fee can only be - /// estimated, because the actual base fee paid by the transaction will - /// depend on the gas prices at the time it's accepted on-chain. - fn compute_fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { - let base_fee = gas_prices.fee(&self.gas_cost()); - base_fee.apply_tier(*fee_tier) - } - - /// Use the provided gas prices and fee tier to refresh the fee estimate for - /// the transaction. - /// - /// If the current fee estimate is too low, it will be increased. In that - /// case, change notes will be adjusted to cover the increase if possible. - pub fn refresh_fee_and_change( - &mut self, - rng: R, - gas_prices: &GasPrices, - fee_tier: &FeeTier, - change_address: &Address, - ) { - // First, refresh the change outputs, to capture any surplus imbalance. - self.refresh_change(rng, &change_address); - - // Next, recompute the fee estimate for the actions and change outputs. - let new_fee = self.compute_fee_estimate(gas_prices, fee_tier); - - // Update the targeted fee with the new estimate. - if new_fee.asset_matches(&self.fee) { - // Take the max of the current fee and the new estimate. This ensures - // that if we already overpaid the fee for some reason, we don't lower it - // and cause the creation of unwanted change outputs. - self.fee.0.amount = std::cmp::max(self.fee.amount(), new_fee.amount()); - } else { - // Otherwise, overwrite the previous fee with the new estimate. - self.fee = new_fee; - } - - // Finally, adjust the change outputs to cover the fee increase if possible. - self.adjust_change_for_imbalance(); - } - - /// Return the balance of the actions in the list, without accounting for fees. - pub fn balance_without_fee(&self) -> Balance { - let mut balance = Balance::zero(); - - for action in &self.actions { - balance += action.balance(); - } - for action in self.change_outputs.values() { - balance += action.balance(); - } - - balance - } - - /// Return the balance of the actions in the list, minus the currently estimated fee - /// required to pay their gas costs. - pub fn balance_with_fee(&self) -> Balance { - self.balance_without_fee() - self.fee.0 - } - - /// Refresh the change notes used to store any surplus imbalance from the - /// actions in the list. - fn refresh_change(&mut self, mut rng: R, change_address: &Address) { - self.change_outputs = BTreeMap::new(); - // For each "provided" balance component, create a change note. - for value in self.balance_with_fee().provided() { - self.change_outputs.insert( - value.asset_id, - OutputPlan::new(&mut rng, value, change_address.clone()), - ); - } - } - - /// Attempt adjust existing change notes to repair imbalance: - /// - /// - cover required balance by decreasing change if possible - /// - cover surplus balance by increasing change if possible - fn adjust_change_for_imbalance(&mut self) { - // We need to grab the current balance upfront before doing modifications. - let balance = self.balance_with_fee(); - - // Sweep surplus balance into existing change notes. - for provided in balance.provided() { - self.change_outputs - .entry(provided.asset_id) - .and_modify(|e| { - e.value.amount += provided.amount; - }); - } - - // Attempt to cover imbalance via existing change notes. - for required in balance.required() { - self.change_outputs - .entry(required.asset_id) - .and_modify(|e| { - // It's important to use saturating_sub here because - // our expectation is that we commonly won't have enough balance. - e.value.amount = e.value.amount.saturating_sub(&required.amount); - }); - } - - // Remove any 0-value change notes we might have created. - self.change_outputs - .retain(|_, output| output.value.amount > Amount::zero()); - } -} - /// Prioritize notes to spend to release value of a specific transaction. /// /// Various logic is possible for note selection. Currently, this method @@ -305,6 +84,50 @@ fn prioritize_and_filter_spendable_notes( filtered } +/// When planning an undelegate action, there may not be metadata yet in the +/// IndexedDB database for the unbonding token that the transaction will output. +/// That's because unbonding tokens are tied to a specific height. If unbonding +/// token metadata for a given validator and a given height doesn't exist yet, +/// we'll generate it here and save it to the database, so that the undelegate +/// action renders correctly in the transaction approval dialog. +async fn save_unbonding_token_metadata_if_needed( + undelegate: &Undelegate, + storage: &IndexedDBStorage, +) -> WasmResult<()> { + let metadata = undelegate.unbonding_token().denom(); + + save_metadata_if_needed(metadata, storage).await +} + +/// When planning Dutch auction-related actions, there will not be metadata yet +/// in the IndexedDB database for the auction NFT that the transaction will +/// output. That's because auction NFTs are derived from information about the +/// auction (for example, an NFT corresponding to a newly started auction is +/// dervived from the auction description parameters, which include a nonce). So +/// we'll generate the metadata here and save it to the database, so that the +/// action renders correctly in the transaction approval dialog. +async fn save_auction_nft_metadata_if_needed( + id: AuctionId, + storage: &IndexedDBStorage, + seq: u64, +) -> WasmResult<()> { + let nft = AuctionNft::new(id, seq); + let metadata = nft.metadata; + + save_metadata_if_needed(metadata, storage).await +} + +async fn save_metadata_if_needed(metadata: Metadata, storage: &IndexedDBStorage) -> WasmResult<()> { + if storage.get_asset(&metadata.id()).await?.is_none() { + let metadata_proto = metadata.to_proto(); + let customized_metadata_proto = customize_symbol_inner(metadata_proto)?; + let customized_metadata = Metadata::try_from(customized_metadata_proto)?; + storage.add_asset(&customized_metadata).await + } else { + Ok(()) + } +} + /// Process a `TransactionPlannerRequest`, returning a `TransactionPlan` #[wasm_bindgen] pub async fn plan_transaction( @@ -346,6 +169,9 @@ pub async fn plan_transaction( .try_into()?; let chain_id: String = app_parameters.chain_id; + let mut transaction_parameters: TransactionParameters = Default::default(); + transaction_parameters.chain_id = chain_id; + let gas_prices: GasPrices = { let gas_prices: penumbra_proto::core::component::fee::v1::GasPrices = serde_wasm_bindgen::from_value( @@ -678,60 +504,8 @@ pub async fn plan_transaction( )); } - let fee = actions.compute_fee_estimate(&gas_prices, &fee_tier); - - let transaction_parameters = TransactionParameters { - expiry_height: request.expiry_height, - chain_id, - fee, - }; - let plan = mem::take(&mut actions).into_plan(&mut OsRng, &fmd_params, transaction_parameters, memo)?; Ok(serde_wasm_bindgen::to_value(&plan)?) } - -/// When planning an undelegate action, there may not be metadata yet in the -/// IndexedDB database for the unbonding token that the transaction will output. -/// That's because unbonding tokens are tied to a specific height. If unbonding -/// token metadata for a given validator and a given height doesn't exist yet, -/// we'll generate it here and save it to the database, so that the undelegate -/// action renders correctly in the transaction approval dialog. -async fn save_unbonding_token_metadata_if_needed( - undelegate: &Undelegate, - storage: &IndexedDBStorage, -) -> WasmResult<()> { - let metadata = undelegate.unbonding_token().denom(); - - save_metadata_if_needed(metadata, storage).await -} - -/// When planning Dutch auction-related actions, there will not be metadata yet -/// in the IndexedDB database for the auction NFT that the transaction will -/// output. That's because auction NFTs are derived from information about the -/// auction (for example, an NFT corresponding to a newly started auction is -/// dervived from the auction description parameters, which include a nonce). So -/// we'll generate the metadata here and save it to the database, so that the -/// action renders correctly in the transaction approval dialog. -async fn save_auction_nft_metadata_if_needed( - id: AuctionId, - storage: &IndexedDBStorage, - seq: u64, -) -> WasmResult<()> { - let nft = AuctionNft::new(id, seq); - let metadata = nft.metadata; - - save_metadata_if_needed(metadata, storage).await -} - -async fn save_metadata_if_needed(metadata: Metadata, storage: &IndexedDBStorage) -> WasmResult<()> { - if storage.get_asset(&metadata.id()).await?.is_none() { - let metadata_proto = metadata.to_proto(); - let customized_metadata_proto = customize_symbol_inner(metadata_proto)?; - let customized_metadata = Metadata::try_from(customized_metadata_proto)?; - storage.add_asset(&customized_metadata).await - } else { - Ok(()) - } -} diff --git a/packages/wasm/crate/tests/build.rs b/packages/wasm/crate/tests/build.rs index 3b345fe6e2..2c970f7f1d 100644 --- a/packages/wasm/crate/tests/build.rs +++ b/packages/wasm/crate/tests/build.rs @@ -2,11 +2,10 @@ extern crate penumbra_wasm; #[cfg(test)] mod tests { - use std::str::FromStr; - use indexed_db_futures::prelude::{ IdbDatabase, IdbObjectStore, IdbQuerySource, IdbTransaction, IdbTransactionMode, }; + use penumbra_asset::STAKING_TOKEN_ASSET_ID; use penumbra_dex::DexParameters; use penumbra_keys::keys::SpendKey; use penumbra_keys::FullViewingKey; @@ -31,6 +30,7 @@ mod tests { }; use prost::Message; use serde::{Deserialize, Serialize}; + use std::str::FromStr; use wasm_bindgen::JsValue; use wasm_bindgen_test::*; @@ -144,6 +144,7 @@ mod tests { compact_block_space_price: 0, verification_price: 0, execution_price: 0, + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), }; // Serialize the parameters into `JsValue`. From d4e1884871b7b940d13a331cd4ba2019f928f01c Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 10 May 2024 10:36:52 -0700 Subject: [PATCH 04/16] update rust deps to v0.75 --- packages/wasm/crate/Cargo.lock | 88 +++++++++++++++++----------------- packages/wasm/crate/Cargo.toml | 28 +++++------ 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/packages/wasm/crate/Cargo.lock b/packages/wasm/crate/Cargo.lock index e02184fdc8..0784b705f0 100644 --- a/packages/wasm/crate/Cargo.lock +++ b/packages/wasm/crate/Cargo.lock @@ -776,8 +776,8 @@ dependencies = [ [[package]] name = "decaf377-fmd" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "ark-ff", "ark-serialize", @@ -790,8 +790,8 @@ dependencies = [ [[package]] name = "decaf377-ka" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "ark-ff", "decaf377 0.5.0", @@ -2087,8 +2087,8 @@ dependencies = [ [[package]] name = "penumbra-asset" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2125,8 +2125,8 @@ dependencies = [ [[package]] name = "penumbra-auction" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2171,8 +2171,8 @@ dependencies = [ [[package]] name = "penumbra-community-pool" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2201,8 +2201,8 @@ dependencies = [ [[package]] name = "penumbra-compact-block" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2233,8 +2233,8 @@ dependencies = [ [[package]] name = "penumbra-dex" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2285,8 +2285,8 @@ dependencies = [ [[package]] name = "penumbra-distributions" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "async-trait", @@ -2301,8 +2301,8 @@ dependencies = [ [[package]] name = "penumbra-fee" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2324,8 +2324,8 @@ dependencies = [ [[package]] name = "penumbra-funding" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "async-trait", @@ -2343,8 +2343,8 @@ dependencies = [ [[package]] name = "penumbra-governance" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2397,8 +2397,8 @@ dependencies = [ [[package]] name = "penumbra-ibc" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2430,8 +2430,8 @@ dependencies = [ [[package]] name = "penumbra-keys" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "aes", "anyhow", @@ -2474,8 +2474,8 @@ dependencies = [ [[package]] name = "penumbra-num" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2510,8 +2510,8 @@ dependencies = [ [[package]] name = "penumbra-proof-params" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ec", @@ -2536,8 +2536,8 @@ dependencies = [ [[package]] name = "penumbra-proto" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "async-trait", @@ -2563,8 +2563,8 @@ dependencies = [ [[package]] name = "penumbra-sct" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2594,8 +2594,8 @@ dependencies = [ [[package]] name = "penumbra-shielded-pool" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2642,8 +2642,8 @@ dependencies = [ [[package]] name = "penumbra-stake" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2684,8 +2684,8 @@ dependencies = [ [[package]] name = "penumbra-tct" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "ark-ed-on-bls12-377", "ark-ff", @@ -2712,8 +2712,8 @@ dependencies = [ [[package]] name = "penumbra-transaction" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "ark-ff", @@ -2763,8 +2763,8 @@ dependencies = [ [[package]] name = "penumbra-txhash" -version = "0.74.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.74.0#8903538cabd2669d0f595012ecb9904039df5112" +version = "0.75.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.75.0#2e122d3c098bb2e1e0df811357aa8d11b5a9bcdb" dependencies = [ "anyhow", "blake2b_simd 1.0.2", diff --git a/packages/wasm/crate/Cargo.toml b/packages/wasm/crate/Cargo.toml index 0b50f4e75f..125003dc82 100644 --- a/packages/wasm/crate/Cargo.toml +++ b/packages/wasm/crate/Cargo.toml @@ -19,20 +19,20 @@ mock-database = [] [dependencies] # TODO: Use `tag` instead of `rev` once auctions land in a tagged release of # core. -penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-auction", default-features = false } -penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-asset" } -penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-compact-block", default-features = false } -penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-dex", default-features = false } -penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-fee", default-features = false } -penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-keys" } -penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-num" } -penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-proof-params", default-features = false } -penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-proto", default-features = false } -penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-sct", default-features = false } -penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-shielded-pool", default-features = false } -penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-stake", default-features = false } -penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-tct" } -penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.74.0", package = "penumbra-transaction", default-features = false } +penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-auction", default-features = false } +penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-asset" } +penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-compact-block", default-features = false } +penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-dex", default-features = false } +penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-fee", default-features = false } +penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-keys" } +penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-num" } +penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-proof-params", default-features = false } +penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-proto", default-features = false } +penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-sct", default-features = false } +penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-shielded-pool", default-features = false } +penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-stake", default-features = false } +penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-tct" } +penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.75.0", package = "penumbra-transaction", default-features = false } anyhow = "1.0.80" ark-ff = { version = "0.4.2", features = ["std"] } From bf3ae2067ae8a5e6edf6d429f69322e742c1827d Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 10 May 2024 13:40:59 -0700 Subject: [PATCH 05/16] fix memo --- packages/wasm/crate/src/planner.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 871e9a778a..d54846e957 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -492,10 +492,10 @@ pub async fn plan_transaction( } } - let mut memo = None; + let mut memo: Option = None; if let Some(pb_memo_plaintext) = request.memo { memo = Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)); - } else { + } else if actions.requires_memo() { // If a memo was not provided, but is required (because we have outputs), // auto-create one with the change address. memo = Some(MemoPlan::new( @@ -508,4 +508,4 @@ pub async fn plan_transaction( mem::take(&mut actions).into_plan(&mut OsRng, &fmd_params, transaction_parameters, memo)?; Ok(serde_wasm_bindgen::to_value(&plan)?) -} \ No newline at end of file +} From 12aa3cef9a2b248f6f67ab4259684d45babc6e28 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 10 May 2024 13:51:26 -0700 Subject: [PATCH 06/16] formatting and clippy --- packages/wasm/crate/src/planner.rs | 27 +++++++++++++-------------- packages/wasm/crate/tests/build.rs | 3 +-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index d54846e957..ef1fd1d7eb 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -169,8 +169,10 @@ pub async fn plan_transaction( .try_into()?; let chain_id: String = app_parameters.chain_id; - let mut transaction_parameters: TransactionParameters = Default::default(); - transaction_parameters.chain_id = chain_id; + let transaction_parameters = TransactionParameters { + chain_id, + ..Default::default() + }; let gas_prices: GasPrices = { let gas_prices: penumbra_proto::core::component::fee::v1::GasPrices = @@ -195,7 +197,7 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("missing address in output"))? .try_into()?; - let output: OutputPlan = OutputPlan::new(&mut OsRng, value, address).into(); + let output: OutputPlan = OutputPlan::new(&mut OsRng, value, address); actions.push(output); } @@ -249,7 +251,7 @@ pub async fn plan_transaction( claim_address, ); - let swap: SwapPlan = SwapPlan::new(&mut OsRng, swap_plaintext).into(); + let swap: SwapPlan = SwapPlan::new(&mut OsRng, swap_plaintext); actions.push(swap); } @@ -271,8 +273,7 @@ pub async fn plan_transaction( epoch_duration: sct_params.epoch_duration, proof_blinding_r: Fq::rand(&mut OsRng), proof_blinding_s: Fq::rand(&mut OsRng), - } - .into(); + }; actions.push(swap_claim); } @@ -286,7 +287,7 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("missing rate data in delegation"))? .try_into()?; - let delegate: Delegate = rate_data.build_delegate(epoch.into(), amount).into(); + let delegate: Delegate = rate_data.build_delegate(epoch.into(), amount); actions.push(delegate); } @@ -299,9 +300,7 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("missing rate data in undelegation"))? .try_into()?; - let undelegate: Undelegate = rate_data - .build_undelegate(epoch.into(), value.amount) - .into(); + let undelegate: Undelegate = rate_data.build_undelegate(epoch.into(), value.amount); save_unbonding_token_metadata_if_needed(&undelegate, &storage).await?; @@ -444,7 +443,7 @@ pub async fn plan_transaction( }; // Compute an initial fee estimate based on the actions we have so far. - actions.refresh_fee_and_change(&mut OsRng, &gas_prices, &fee_tier, &change_address); + actions.refresh_fee_and_change(OsRng, &gas_prices, &fee_tier, &change_address); let mut notes_by_asset_id = BTreeMap::new(); for required in actions.balance_with_fee().required() { @@ -484,9 +483,9 @@ pub async fn plan_transaction( actions.push(SpendPlan::new(&mut OsRng, note.note, note.position)); // Refresh the fee estimate and change outputs. - actions.refresh_fee_and_change(&mut OsRng, &gas_prices, &fee_tier, &change_address); + actions.refresh_fee_and_change(OsRng, &gas_prices, &fee_tier, &change_address); - iterations = iterations + 1; + iterations += 1; if iterations > 100 { return Err(anyhow!("failed to plan transaction after 100 iterations").into()); } @@ -505,7 +504,7 @@ pub async fn plan_transaction( } let plan = - mem::take(&mut actions).into_plan(&mut OsRng, &fmd_params, transaction_parameters, memo)?; + mem::take(&mut actions).into_plan(OsRng, &fmd_params, transaction_parameters, memo)?; Ok(serde_wasm_bindgen::to_value(&plan)?) } diff --git a/packages/wasm/crate/tests/build.rs b/packages/wasm/crate/tests/build.rs index ca743765a7..788748dae1 100644 --- a/packages/wasm/crate/tests/build.rs +++ b/packages/wasm/crate/tests/build.rs @@ -140,12 +140,11 @@ mod tests { as_of_block_height: 1u64, }; let gas_prices = GasPrices { - asset_id: None, + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), block_space_price: 0, compact_block_space_price: 0, verification_price: 0, execution_price: 0, - asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), }; // Serialize the parameters into `JsValue`. From d3b9d714ffaa4477e625c27b22e998a145b10a26 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 10 May 2024 14:11:55 -0700 Subject: [PATCH 07/16] minor organization --- packages/wasm/crate/src/planner.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index ef1fd1d7eb..f72e61224a 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -139,15 +139,18 @@ pub async fn plan_transaction( let request = TransactionPlannerRequest::decode(request)?; - let source_address_index: AddressIndex = request + let mut source_address_index: AddressIndex = request .source .map(TryInto::try_into) .transpose()? .unwrap_or_default(); - let fvk: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; + // Wipe out the randomizer for the provided source, since + // 1. All randomizers correspond to the same account + // 2. Using one-time addresses for change addresses is undesirable. + source_address_index.randomizer = [0u8; 12]; - // should ignore the randomizer for change_address, there is no point using ephemeral address + let fvk: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; let (change_address, _) = fvk .incoming() .payment_address(source_address_index.account.into()); @@ -163,10 +166,12 @@ pub async fn plan_transaction( .get_app_params() .await? .ok_or_else(|| anyhow!("AppParameters not available"))?; + let sct_params: SctParameters = app_parameters .sct_params .ok_or_else(|| anyhow!("SctParameters not available"))? .try_into()?; + let chain_id: String = app_parameters.chain_id; let transaction_parameters = TransactionParameters { @@ -196,7 +201,6 @@ pub async fn plan_transaction( let address = address .ok_or_else(|| anyhow!("missing address in output"))? .try_into()?; - let output: OutputPlan = OutputPlan::new(&mut OsRng, value, address); actions.push(output); @@ -250,7 +254,6 @@ pub async fn plan_transaction( fee, claim_address, ); - let swap: SwapPlan = SwapPlan::new(&mut OsRng, swap_plaintext); actions.push(swap); @@ -286,8 +289,8 @@ pub async fn plan_transaction( let rate_data: RateData = rate_data .ok_or_else(|| anyhow!("missing rate data in delegation"))? .try_into()?; - let delegate: Delegate = rate_data.build_delegate(epoch.into(), amount); + actions.push(delegate); } @@ -299,9 +302,7 @@ pub async fn plan_transaction( let rate_data: RateData = rate_data .ok_or_else(|| anyhow!("missing rate data in undelegation"))? .try_into()?; - let undelegate: Undelegate = rate_data.build_undelegate(epoch.into(), value.amount); - save_unbonding_token_metadata_if_needed(&undelegate, &storage).await?; actions.push(undelegate); @@ -395,13 +396,7 @@ pub async fn plan_transaction( nonce, }; - save_auction_nft_metadata_if_needed( - description.id(), - &storage, - // When scheduling a Dutch auction, the sequence number is always 0 - 0, - ) - .await?; + save_auction_nft_metadata_if_needed(description.id(), &storage, 0).await?; actions.push(ActionPlan::ActionDutchAuctionSchedule( ActionDutchAuctionSchedule { description }, @@ -425,8 +420,6 @@ pub async fn plan_transaction( })); } - // TODO (Jesse): Handle Dutch auction withdraws - // Phase 2: balance the transaction with information from the view service. // // It's possible that adding spends could increase the gas, increasing @@ -491,6 +484,7 @@ pub async fn plan_transaction( } } + // Add memo to the transaction plan. let mut memo: Option = None; if let Some(pb_memo_plaintext) = request.memo { memo = Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)); From c4cb5eebf9875934689389f21361758ea88471f4 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 10 May 2024 14:14:45 -0700 Subject: [PATCH 08/16] spelling --- packages/wasm/crate/src/planner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index f72e61224a..925c993c94 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -462,7 +462,7 @@ pub async fn plan_transaction( // Find a single note to spend towards the required balance. let note = notes_by_asset_id .get_mut(&required.asset_id) - .expect("we already made a notesrequest for each required asset") + .expect("we already made a notes request for each required asset") .pop() .ok_or_else(|| { anyhow!( From 807ed562758cf4d28614d118d1bfdd7a0085c665 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 11 May 2024 10:00:43 -0700 Subject: [PATCH 09/16] manually set gas prices in indexdb --- packages/query/src/block-processor.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index b4075aefef..6504db9f10 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -44,6 +44,7 @@ import { getAuctionId, getAuctionNftMetadata } from '@penumbra-zone/wasm/auction import { AuctionId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/auction/v1alpha1/auction_pb'; import { auctionIdFromBech32 } from '@penumbra-zone/bech32m/pauctid'; import { ScanBlockResult } from '@penumbra-zone/types/state-commitment-tree'; +import { GasPrices } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/fee/v1/fee_pb'; declare global { // `var` required for global declaration (as let/const are block scoped) @@ -201,6 +202,14 @@ export class BlockProcessor implements BlockProcessorInterface { await this.indexedDb.saveGasPrices(compactBlock.gasPrices); } + // manually (temporarily) set non-zero gas prices in index db for testing purposes. + await this.indexedDb.saveGasPrices(new GasPrices({ + verificationPrice: 1n, + executionPrice: 1n, + blockSpacePrice: 1n, + compactBlockSpacePrice: 1n, + })); + // wasm view server scan // - decrypts new notes // - decrypts new swaps From f022bf0227277ed9e2ddbbef5f3b0766f8163f34 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 11 May 2024 15:57:23 -0700 Subject: [PATCH 10/16] add prepaid fee logic to planner --- apps/minifront/src/state/swap.ts | 4 +- packages/wasm/crate/src/planner.rs | 68 +++++++++++++++++------------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/apps/minifront/src/state/swap.ts b/apps/minifront/src/state/swap.ts index c9315f25c2..a9469553cf 100644 --- a/apps/minifront/src/state/swap.ts +++ b/apps/minifront/src/state/swap.ts @@ -177,9 +177,7 @@ const assembleSwapRequest = async ({ assetIn, amount, assetOut }: SwapSlice) => assetId: getAssetIdFromValueView(assetIn.balanceView), }, claimAddress: await getAddressByIndex(addressIndex.account), - // TODO: Calculate this properly in subsequent PR - // Asset Id should almost certainly be upenumbra, - // may need to indicate native denom in registry + // The prepaid fee will instead be calculated directly in the rust planner logic. fee: { amount: { hi: 0n, diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 925c993c94..6c93a09610 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -31,6 +31,7 @@ use penumbra_sct::params::SctParameters; use penumbra_shielded_pool::{fmd, OutputPlan, SpendPlan}; use penumbra_stake::rate::RateData; use penumbra_stake::{Delegate, IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; +use penumbra_transaction::gas::swap_claim_gas_cost; use penumbra_transaction::memo::MemoPlaintext; use penumbra_transaction::ActionList; use penumbra_transaction::{plan::MemoPlan, ActionPlan, TransactionParameters}; @@ -151,6 +152,8 @@ pub async fn plan_transaction( source_address_index.randomizer = [0u8; 12]; let fvk: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; + + // Compute the change address for this transaction. let (change_address, _) = fvk .incoming() .payment_address(source_address_index.account.into()); @@ -190,7 +193,15 @@ pub async fn plan_transaction( gas_prices.try_into()? }; - let mut actions = ActionList::default(); + let fee_tier = match request.fee_mode { + None => FeeTier::default(), + Some(tpr::FeeMode::AutoFee(tier)) => tier.try_into()?, + Some(tpr::FeeMode::ManualFee(_)) => { + return Err(anyhow!("Manual fee mode not yet implemented").into()); + } + }; + + let mut actions_list = ActionList::default(); // Phase 1: process all of the user-supplied intents into complete action plans. @@ -203,13 +214,13 @@ pub async fn plan_transaction( .try_into()?; let output: OutputPlan = OutputPlan::new(&mut OsRng, value, address); - actions.push(output); + actions_list.push(output); } for tpr::Swap { value, target_asset, - fee, + fee: _, claim_address, } in request.swaps { @@ -219,13 +230,16 @@ pub async fn plan_transaction( let target_asset = target_asset .ok_or_else(|| anyhow!("missing target asset in swap"))? .try_into()?; - let fee = fee - .ok_or_else(|| anyhow!("missing fee in swap"))? - .try_into()?; let claim_address = claim_address .ok_or_else(|| anyhow!("missing claim address in swap"))? .try_into()?; + // This is the prepaid fee for the swap claim. We don't expect much of a drift in gas + // prices in a few blocks, and the fee tier adjustments should be enough to cover it. + let estimated_claim_fee = gas_prices + .fee(&swap_claim_gas_cost()) + .apply_tier(fee_tier.into()); + // Determine the canonical order for the assets being swapped. // This will determine whether the input amount is assigned to delta_1 or delta_2. let trading_pair = TradingPair::new(value.asset_id, target_asset); @@ -251,12 +265,12 @@ pub async fn plan_transaction( trading_pair, delta_1, delta_2, - fee, + estimated_claim_fee, claim_address, ); let swap: SwapPlan = SwapPlan::new(&mut OsRng, swap_plaintext); - actions.push(swap); + actions_list.push(swap); } for tpr::SwapClaim { swap_commitment } in request.swap_claims { @@ -278,7 +292,7 @@ pub async fn plan_transaction( proof_blinding_s: Fq::rand(&mut OsRng), }; - actions.push(swap_claim); + actions_list.push(swap_claim); } for tpr::Delegate { amount, rate_data } in request.delegations { @@ -291,7 +305,7 @@ pub async fn plan_transaction( .try_into()?; let delegate: Delegate = rate_data.build_delegate(epoch.into(), amount); - actions.push(delegate); + actions_list.push(delegate); } for tpr::Undelegate { value, rate_data } in request.undelegations { @@ -305,7 +319,7 @@ pub async fn plan_transaction( let undelegate: Undelegate = rate_data.build_undelegate(epoch.into(), value.amount); save_unbonding_token_metadata_if_needed(&undelegate, &storage).await?; - actions.push(undelegate); + actions_list.push(undelegate); } for tpr::UndelegateClaim { @@ -336,7 +350,7 @@ pub async fn plan_transaction( proof_blinding_s: Fq::rand(&mut OsRng), }; - actions.push(ActionPlan::UndelegateClaim(undelegate_claim_plan)); + actions_list.push(ActionPlan::UndelegateClaim(undelegate_claim_plan)); } #[allow(clippy::never_loop)] @@ -345,7 +359,7 @@ pub async fn plan_transaction( } for ics20_withdrawal in request.ics20_withdrawals { - actions.push(ActionPlan::Ics20Withdrawal(ics20_withdrawal.try_into()?)); + actions_list.push(ActionPlan::Ics20Withdrawal(ics20_withdrawal.try_into()?)); } #[allow(clippy::never_loop)] @@ -398,7 +412,7 @@ pub async fn plan_transaction( save_auction_nft_metadata_if_needed(description.id(), &storage, 0).await?; - actions.push(ActionPlan::ActionDutchAuctionSchedule( + actions_list.push(ActionPlan::ActionDutchAuctionSchedule( ActionDutchAuctionSchedule { description }, )); } @@ -415,11 +429,13 @@ pub async fn plan_transaction( ) .await?; - actions.push(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { + actions_list.push(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { auction_id, })); } + println!("planner log!"); + // Phase 2: balance the transaction with information from the view service. // // It's possible that adding spends could increase the gas, increasing @@ -427,19 +443,11 @@ pub async fn plan_transaction( // need to query all the notes we'll use for planning upfront, so we // don't accidentally try to use the same one twice. - let fee_tier = match request.fee_mode { - None => FeeTier::default(), - Some(tpr::FeeMode::AutoFee(tier)) => tier.try_into()?, - Some(tpr::FeeMode::ManualFee(_)) => { - return Err(anyhow!("Manual fee mode not yet implemented").into()); - } - }; - // Compute an initial fee estimate based on the actions we have so far. - actions.refresh_fee_and_change(OsRng, &gas_prices, &fee_tier, &change_address); + actions_list.refresh_fee_and_change(OsRng, &gas_prices, &fee_tier, &change_address); let mut notes_by_asset_id = BTreeMap::new(); - for required in actions.balance_with_fee().required() { + for required in actions_list.balance_with_fee().required() { // Find all the notes of this asset in the source account. let records = storage .get_notes(NotesRequest { @@ -458,7 +466,7 @@ pub async fn plan_transaction( let mut iterations = 0usize; // Now iterate over the action list's imbalances to balance the transaction. - while let Some(required) = actions.balance_with_fee().required().next() { + while let Some(required) = actions_list.balance_with_fee().required().next() { // Find a single note to spend towards the required balance. let note = notes_by_asset_id .get_mut(&required.asset_id) @@ -473,10 +481,10 @@ pub async fn plan_transaction( })?; // Add a spend for that note to the action list. - actions.push(SpendPlan::new(&mut OsRng, note.note, note.position)); + actions_list.push(SpendPlan::new(&mut OsRng, note.note, note.position)); // Refresh the fee estimate and change outputs. - actions.refresh_fee_and_change(OsRng, &gas_prices, &fee_tier, &change_address); + actions_list.refresh_fee_and_change(OsRng, &gas_prices, &fee_tier, &change_address); iterations += 1; if iterations > 100 { @@ -488,7 +496,7 @@ pub async fn plan_transaction( let mut memo: Option = None; if let Some(pb_memo_plaintext) = request.memo { memo = Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)); - } else if actions.requires_memo() { + } else if actions_list.requires_memo() { // If a memo was not provided, but is required (because we have outputs), // auto-create one with the change address. memo = Some(MemoPlan::new( @@ -498,7 +506,7 @@ pub async fn plan_transaction( } let plan = - mem::take(&mut actions).into_plan(OsRng, &fmd_params, transaction_parameters, memo)?; + mem::take(&mut actions_list).into_plan(OsRng, &fmd_params, transaction_parameters, memo)?; Ok(serde_wasm_bindgen::to_value(&plan)?) } From 31f1bb2545b6be3e3710dc4b7476d84736235ada Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 11 May 2024 16:02:53 -0700 Subject: [PATCH 11/16] linting --- packages/query/src/block-processor.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index 6504db9f10..961481031e 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -203,12 +203,14 @@ export class BlockProcessor implements BlockProcessorInterface { } // manually (temporarily) set non-zero gas prices in index db for testing purposes. - await this.indexedDb.saveGasPrices(new GasPrices({ - verificationPrice: 1n, - executionPrice: 1n, - blockSpacePrice: 1n, - compactBlockSpacePrice: 1n, - })); + await this.indexedDb.saveGasPrices( + new GasPrices({ + verificationPrice: 1n, + executionPrice: 1n, + blockSpacePrice: 1n, + compactBlockSpacePrice: 1n, + }), + ); // wasm view server scan // - decrypts new notes From 3647965d4f702e85332f650bbd967642a52007a4 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 11 May 2024 16:16:55 -0700 Subject: [PATCH 12/16] fmt and clippy --- packages/wasm/crate/src/planner.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 6c93a09610..1c33b35761 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -236,9 +236,7 @@ pub async fn plan_transaction( // This is the prepaid fee for the swap claim. We don't expect much of a drift in gas // prices in a few blocks, and the fee tier adjustments should be enough to cover it. - let estimated_claim_fee = gas_prices - .fee(&swap_claim_gas_cost()) - .apply_tier(fee_tier.into()); + let estimated_claim_fee = gas_prices.fee(&swap_claim_gas_cost()).apply_tier(fee_tier); // Determine the canonical order for the assets being swapped. // This will determine whether the input amount is assigned to delta_1 or delta_2. From 53dce2ad5b0af2f0f7ed4f15db72253c0117e54a Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 13 May 2024 10:37:29 -0700 Subject: [PATCH 13/16] removed extra types --- packages/wasm/crate/src/planner.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 1c33b35761..ff1ff87826 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -30,7 +30,7 @@ use penumbra_proto::DomainType; use penumbra_sct::params::SctParameters; use penumbra_shielded_pool::{fmd, OutputPlan, SpendPlan}; use penumbra_stake::rate::RateData; -use penumbra_stake::{Delegate, IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; +use penumbra_stake::{IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; use penumbra_transaction::gas::swap_claim_gas_cost; use penumbra_transaction::memo::MemoPlaintext; use penumbra_transaction::ActionList; @@ -212,7 +212,7 @@ pub async fn plan_transaction( let address = address .ok_or_else(|| anyhow!("missing address in output"))? .try_into()?; - let output: OutputPlan = OutputPlan::new(&mut OsRng, value, address); + let output = OutputPlan::new(&mut OsRng, value, address); actions_list.push(output); } @@ -266,7 +266,7 @@ pub async fn plan_transaction( estimated_claim_fee, claim_address, ); - let swap: SwapPlan = SwapPlan::new(&mut OsRng, swap_plaintext); + let swap = SwapPlan::new(&mut OsRng, swap_plaintext); actions_list.push(swap); } @@ -281,7 +281,7 @@ pub async fn plan_transaction( .ok_or_else(|| anyhow!("Swap record not found"))? .try_into()?; - let swap_claim: SwapClaimPlan = SwapClaimPlan { + let swap_claim = SwapClaimPlan { swap_plaintext: swap_record.swap, position: swap_record.position, output_data: swap_record.output_data, @@ -301,7 +301,7 @@ pub async fn plan_transaction( let rate_data: RateData = rate_data .ok_or_else(|| anyhow!("missing rate data in delegation"))? .try_into()?; - let delegate: Delegate = rate_data.build_delegate(epoch.into(), amount); + let delegate = rate_data.build_delegate(epoch.into(), amount); actions_list.push(delegate); } @@ -314,7 +314,7 @@ pub async fn plan_transaction( let rate_data: RateData = rate_data .ok_or_else(|| anyhow!("missing rate data in undelegation"))? .try_into()?; - let undelegate: Undelegate = rate_data.build_undelegate(epoch.into(), value.amount); + let undelegate = rate_data.build_undelegate(epoch.into(), value.amount); save_unbonding_token_metadata_if_needed(&undelegate, &storage).await?; actions_list.push(undelegate); From 22c5234d71c369cf0b05fde2f242afbc56be5f24 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 13 May 2024 10:38:15 -0700 Subject: [PATCH 14/16] remove log --- packages/wasm/crate/src/planner.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index ff1ff87826..f3a5c2381d 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -432,8 +432,6 @@ pub async fn plan_transaction( })); } - println!("planner log!"); - // Phase 2: balance the transaction with information from the view service. // // It's possible that adding spends could increase the gas, increasing From 977befe3662da6bd8ceba6fdf86eaa9678dff1cb Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 13 May 2024 11:14:48 -0700 Subject: [PATCH 15/16] address comments --- packages/wasm/crate/src/planner.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index f3a5c2381d..b91426f289 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -489,17 +489,16 @@ pub async fn plan_transaction( } // Add memo to the transaction plan. - let mut memo: Option = None; - if let Some(pb_memo_plaintext) = request.memo { - memo = Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)); + let memo = if let Some(pb_memo_plaintext) = request.memo { + Some(MemoPlan::new(&mut OsRng, pb_memo_plaintext.try_into()?)) } else if actions_list.requires_memo() { // If a memo was not provided, but is required (because we have outputs), // auto-create one with the change address. - memo = Some(MemoPlan::new( - &mut OsRng, - MemoPlaintext::new(change_address, String::new())?, - )); - } + let plaintext = MemoPlaintext::new(change_address, String::new())?; + Some(MemoPlan::new(&mut OsRng, plaintext)) + } else { + None + }; let plan = mem::take(&mut actions_list).into_plan(OsRng, &fmd_params, transaction_parameters, memo)?; From 4724324d5be6ecd7e1fd0019358578b83b946249 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 13 May 2024 13:35:57 -0700 Subject: [PATCH 16/16] address comments --- apps/minifront/src/state/swap.ts | 7 ------- packages/query/src/block-processor.ts | 11 ----------- packages/storage/src/indexed-db/index.ts | 19 +++++++++++++++++++ packages/wasm/crate/src/planner.rs | 13 +++++++++---- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/apps/minifront/src/state/swap.ts b/apps/minifront/src/state/swap.ts index a9469553cf..1d4a203912 100644 --- a/apps/minifront/src/state/swap.ts +++ b/apps/minifront/src/state/swap.ts @@ -177,13 +177,6 @@ const assembleSwapRequest = async ({ assetIn, amount, assetOut }: SwapSlice) => assetId: getAssetIdFromValueView(assetIn.balanceView), }, claimAddress: await getAddressByIndex(addressIndex.account), - // The prepaid fee will instead be calculated directly in the rust planner logic. - fee: { - amount: { - hi: 0n, - lo: 0n, - }, - }, }, ], source: getAddressIndex(assetIn.accountAddress), diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index 961481031e..b4075aefef 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -44,7 +44,6 @@ import { getAuctionId, getAuctionNftMetadata } from '@penumbra-zone/wasm/auction import { AuctionId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/auction/v1alpha1/auction_pb'; import { auctionIdFromBech32 } from '@penumbra-zone/bech32m/pauctid'; import { ScanBlockResult } from '@penumbra-zone/types/state-commitment-tree'; -import { GasPrices } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/fee/v1/fee_pb'; declare global { // `var` required for global declaration (as let/const are block scoped) @@ -202,16 +201,6 @@ export class BlockProcessor implements BlockProcessorInterface { await this.indexedDb.saveGasPrices(compactBlock.gasPrices); } - // manually (temporarily) set non-zero gas prices in index db for testing purposes. - await this.indexedDb.saveGasPrices( - new GasPrices({ - verificationPrice: 1n, - executionPrice: 1n, - blockSpacePrice: 1n, - compactBlockSpacePrice: 1n, - }), - ); - // wasm view server scan // - decrypts new notes // - decrypts new swaps diff --git a/packages/storage/src/indexed-db/index.ts b/packages/storage/src/indexed-db/index.ts index 1007a8986e..c7cf797361 100644 --- a/packages/storage/src/indexed-db/index.ts +++ b/packages/storage/src/indexed-db/index.ts @@ -137,6 +137,9 @@ export class IndexedDb implements IndexedDbInterface { const existing0thEpoch = await instance.getEpochByHeight(0n); if (!existing0thEpoch) await instance.addEpoch(0n); // Create first epoch + // set non-zero gas prices in indexDB since the testnet has not yet enabled gas fees. + await instance.initGasPrices(); + return instance; } @@ -279,6 +282,22 @@ export class IndexedDb implements IndexedDbInterface { ); } + async initGasPrices() { + const savedGasPrices = await this.getGasPrices(); + // These are arbitrarily set, but can take on any value. + // The gas prices set here will determine the fees to use Penumbra. + if (!savedGasPrices) { + await this.saveGasPrices( + new GasPrices({ + verificationPrice: 1n, + executionPrice: 1n, + blockSpacePrice: 1n, + compactBlockSpacePrice: 1n, + }), + ); + } + } + async *iterateTransactions() { yield* new ReadableStream( new IdbCursorSource(this.db.transaction('TRANSACTIONS').store.openCursor(), TransactionInfo), diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index b91426f289..a8c510e04b 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -220,6 +220,11 @@ pub async fn plan_transaction( for tpr::Swap { value, target_asset, + // The prepaid fee will instead be calculated directly in the rust planner logic. + // + // TODO: external consumers of prax may decide to enable manaual fees, and there may be + // additional checks required to make sure the fees satisfy to the balancing checks + // for swap claims. fee: _, claim_address, } in request.swaps @@ -466,13 +471,12 @@ pub async fn plan_transaction( // Find a single note to spend towards the required balance. let note = notes_by_asset_id .get_mut(&required.asset_id) - .expect("we already made a notes request for each required asset") - .pop() + .and_then(|notes| notes.pop()) .ok_or_else(|| { anyhow!( - "ran out of notes to spend while planning transaction, need {} of asset {}", - required.amount, + "Failed to retrieve or ran out of notes for asset {}, required amount {}", required.asset_id, + required.amount ) })?; @@ -500,6 +504,7 @@ pub async fn plan_transaction( None }; + // Reset the planner in case it were reused. let plan = mem::take(&mut actions_list).into_plan(OsRng, &fmd_params, transaction_parameters, memo)?;