From 5bfa65e5fd4c135dbceec8e31e283c16716590a6 Mon Sep 17 00:00:00 2001 From: Valentine Date: Thu, 16 Nov 2023 01:48:54 +0200 Subject: [PATCH 1/3] added source control support to wasm planner --- crates/wasm/src/planner.rs | 16 +++++++++----- crates/wasm/src/storage.rs | 39 +++++++++++++++++++++++---------- crates/wasm/src/wasm_planner.rs | 12 ++++++---- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/crates/wasm/src/planner.rs b/crates/wasm/src/planner.rs index 3d9af7e8eb..90c736c85b 100644 --- a/crates/wasm/src/planner.rs +++ b/crates/wasm/src/planner.rs @@ -27,6 +27,7 @@ use penumbra_governance::{ use penumbra_ibc::IbcRelay; use penumbra_keys::Address; use penumbra_num::Amount; +use penumbra_proto::core::keys::v1alpha1::AddressIndex; use penumbra_proto::view::v1alpha1::{NotesForVotingRequest, NotesRequest}; use penumbra_shielded_pool::{Ics20Withdrawal, Note, OutputPlan, SpendPlan}; use penumbra_stake::{rate::RateData, validator}; @@ -94,14 +95,17 @@ impl Planner { } /// Get all the note requests necessary to fulfill the current [`Balance`]. - pub fn notes_requests(&self) -> (Vec, Vec) { + pub fn notes_requests( + &self, + source: Option, + ) -> (Vec, Vec) { ( self.balance .required() .map(|Value { asset_id, amount }| NotesRequest { wallet_id: None, asset_id: Some(asset_id.into()), - address_index: None, + address_index: source.clone(), amount_to_spend: Some(amount.into()), include_spent: false, }) @@ -115,10 +119,10 @@ impl Planner { start_block_height, .. }, )| NotesForVotingRequest { - wallet_id: None, - votable_at_height: *start_block_height, - address_index: None, - }, + wallet_id: None, + votable_at_height: *start_block_height, + address_index: source.clone(), + }, ) .collect(), ) diff --git a/crates/wasm/src/storage.rs b/crates/wasm/src/storage.rs index d4b1baccd5..3c44abed02 100644 --- a/crates/wasm/src/storage.rs +++ b/crates/wasm/src/storage.rs @@ -52,18 +52,35 @@ impl IndexedDBStorage { .into_iter() .map(|js_value| serde_wasm_bindgen::from_value(js_value).ok()) .filter_map(|note_option| { - note_option.and_then(|note: SpendableNoteRecord| match request.asset_id.clone() { - Some(asset_id) => { - if note.note.asset_id() == asset_id.try_into().expect("Invalid asset id") - && note.height_spent.is_none() - { - Some(note) - } else { - None + note_option + .and_then(|note: SpendableNoteRecord| match request.asset_id.clone() { + Some(asset_id) => { + if note.note.asset_id() + == asset_id.try_into().expect("Invalid asset id") + && note.height_spent.is_none() + { + Some(note) + } else { + None + } } - } - None => Some(note), - }) + None => Some(note), + }) + .and_then( + |note: SpendableNoteRecord| match request.address_index.clone() { + Some(address_index) => { + if note + .address_index + .eq(&address_index.try_into().expect("invalid address index")) + { + Some(note) + } else { + None + } + } + None => Some(note), + }, + ) }) .collect(); diff --git a/crates/wasm/src/wasm_planner.rs b/crates/wasm/src/wasm_planner.rs index c875f56d14..2aebcfdd25 100644 --- a/crates/wasm/src/wasm_planner.rs +++ b/crates/wasm/src/wasm_planner.rs @@ -9,7 +9,7 @@ use penumbra_dex::swap_claim::SwapClaimPlan; use penumbra_proto::core::asset::v1alpha1::{DenomMetadata, Value}; use penumbra_proto::core::component::fee::v1alpha1::{Fee, GasPrices}; use penumbra_proto::core::component::ibc::v1alpha1::Ics20Withdrawal; -use penumbra_proto::core::keys::v1alpha1::Address; +use penumbra_proto::core::keys::v1alpha1::{Address, AddressIndex}; use penumbra_proto::core::transaction::v1alpha1::MemoPlaintext; use penumbra_proto::crypto::tct::v1alpha1::StateCommitment; use penumbra_proto::DomainType; @@ -184,11 +184,13 @@ impl WasmPlanner { } /// Builds transaction plan. - /// Refund address provided in the case there is extra balances to be returned. + /// Refund address provided in the case there is extra balances to be returned + // If source present, only spends funds from the given account. /// Arguments: /// refund_address: `Address` + /// source: `Option` /// Returns: `TransactionPlan` - pub async fn plan(&mut self, refund_address: JsValue) -> WasmResult { + pub async fn plan(&mut self, refund_address: JsValue, source: JsValue) -> WasmResult { utils::set_panic_hook(); // Calculate the gas that needs to be paid for the transaction based on the configured gas prices. @@ -199,7 +201,9 @@ impl WasmPlanner { let mut spendable_notes = Vec::new(); - let (spendable_requests, _) = self.planner.notes_requests(); + let source_address_index: Option = serde_wasm_bindgen::from_value(source)?; + + let (spendable_requests, _) = self.planner.notes_requests(source_address_index); for request in spendable_requests { let notes = self.storage.get_notes(request); spendable_notes.extend(notes.await?); From dee636215cf3b28054f3dc6024bdf57f518b0e2a Mon Sep 17 00:00:00 2001 From: Valentine Date: Thu, 16 Nov 2023 02:13:39 +0200 Subject: [PATCH 2/3] fmt --- crates/wasm/src/planner.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/wasm/src/planner.rs b/crates/wasm/src/planner.rs index 90c736c85b..33bb88f98b 100644 --- a/crates/wasm/src/planner.rs +++ b/crates/wasm/src/planner.rs @@ -119,10 +119,10 @@ impl Planner { start_block_height, .. }, )| NotesForVotingRequest { - wallet_id: None, - votable_at_height: *start_block_height, - address_index: source.clone(), - }, + wallet_id: None, + votable_at_height: *start_block_height, + address_index: source.clone(), + }, ) .collect(), ) From 17d5bd2224e211cc3d5b282e081f481703648fbe Mon Sep 17 00:00:00 2001 From: Valentine Date: Thu, 16 Nov 2023 14:25:16 +0200 Subject: [PATCH 3/3] simplify nested closures --- crates/wasm/src/storage.rs | 69 ++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/crates/wasm/src/storage.rs b/crates/wasm/src/storage.rs index 3c44abed02..e37915873d 100644 --- a/crates/wasm/src/storage.rs +++ b/crates/wasm/src/storage.rs @@ -1,6 +1,7 @@ use indexed_db_futures::prelude::OpenDbRequest; use indexed_db_futures::{IdbDatabase, IdbQuerySource}; use serde::{Deserialize, Serialize}; +use wasm_bindgen::JsValue; use web_sys::IdbTransactionMode::Readwrite; use penumbra_asset::asset::{DenomMetadata, Id}; @@ -10,7 +11,7 @@ use penumbra_proto::DomainType; use penumbra_sct::Nullifier; use penumbra_shielded_pool::{note, Note}; -use crate::error::WasmResult; +use crate::error::{WasmError, WasmResult}; use crate::note_record::SpendableNoteRecord; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -46,45 +47,39 @@ impl IndexedDBStorage { .transaction_on_one(&self.constants.tables.spendable_notes)?; let store = idb_tx.object_store(&self.constants.tables.spendable_notes)?; - let values = store.get_all()?.await?; - - let notes: Vec = values + let raw_values = store.get_all()?.await?; + let parsed_notes = raw_values .into_iter() - .map(|js_value| serde_wasm_bindgen::from_value(js_value).ok()) - .filter_map(|note_option| { - note_option - .and_then(|note: SpendableNoteRecord| match request.asset_id.clone() { - Some(asset_id) => { - if note.note.asset_id() - == asset_id.try_into().expect("Invalid asset id") - && note.height_spent.is_none() - { - Some(note) - } else { - None - } - } - None => Some(note), - }) - .and_then( - |note: SpendableNoteRecord| match request.address_index.clone() { - Some(address_index) => { - if note - .address_index - .eq(&address_index.try_into().expect("invalid address index")) - { - Some(note) - } else { - None - } - } - None => Some(note), - }, - ) - }) + .filter_map(|js_value| self.parse_note(js_value, &request).ok()) .collect(); - Ok(notes) + Ok(parsed_notes) + } + + fn parse_note( + &self, + js_value: JsValue, + request: &NotesRequest, + ) -> WasmResult { + let note: SpendableNoteRecord = serde_wasm_bindgen::from_value(js_value)?; + + let asset_id_matches = match &request.asset_id { + Some(asset_id) => note.note.asset_id() == asset_id.clone().try_into()?, + None => true, + }; + + let address_index_matches = match &request.address_index { + Some(address_index) => note.address_index.eq(&address_index.clone().try_into()?), + None => true, + }; + + if asset_id_matches && address_index_matches && note.height_spent.is_none() { + Ok(note) + } else { + Err(WasmError::Anyhow(anyhow::anyhow!( + "Note does not match the request" + ))) + } } pub async fn get_asset(&self, id: &Id) -> WasmResult> {