diff --git a/.changeset/smart-vans-think.md b/.changeset/smart-vans-think.md new file mode 100644 index 0000000000..316f758404 --- /dev/null +++ b/.changeset/smart-vans-think.md @@ -0,0 +1,5 @@ +--- +'@penumbra-zone/wasm': minor +--- + +migrate wasm unit test for action building to use mockDb diff --git a/packages/types/src/indexed-db.ts b/packages/types/src/indexed-db.ts index 59910f4abd..45395dfd26 100644 --- a/packages/types/src/indexed-db.ts +++ b/packages/types/src/indexed-db.ts @@ -322,4 +322,7 @@ export const IDB_TABLES: Tables = { validator_infos: 'VALIDATOR_INFOS', transactions: 'TRANSACTIONS', full_sync_height: 'FULL_SYNC_HEIGHT', + tree_commitments: 'TREE_COMMITMENTS', + tree_last_position: 'TREE_LAST_POSITION', + tree_last_forgotten: 'TREE_LAST_FORGOTTEN', }; diff --git a/packages/wasm/crate/src/build.rs b/packages/wasm/crate/src/build.rs index 39e4aa2344..6d777587bb 100644 --- a/packages/wasm/crate/src/build.rs +++ b/packages/wasm/crate/src/build.rs @@ -1,20 +1,19 @@ +use crate::error::WasmResult; +use crate::utils; use penumbra_keys::FullViewingKey; use penumbra_proto::DomainType; use penumbra_transaction::{ plan::{ActionPlan, TransactionPlan}, - WitnessData, + Action, AuthorizationData, Transaction, WitnessData, }; -use wasm_bindgen::prelude::wasm_bindgen; - -use crate::error::WasmResult; -use crate::utils; +use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; /// Builds a planned [`Action`] specified by /// the [`ActionPlan`] in a [`TransactionPlan`]. /// Arguments: /// transaction_plan: `TransactionPlan` /// action_plan: `ActionPlan` -/// full_viewing_key: `byte representation inner FullViewingKey` +/// full_viewing_key: `FullViewingKey` /// witness_data: `WitnessData`` /// Returns: `Action` #[wasm_bindgen] @@ -28,10 +27,103 @@ pub fn build_action( let transaction_plan = TransactionPlan::decode(transaction_plan)?; let witness = WitnessData::decode(witness_data)?; let action_plan = ActionPlan::decode(action_plan)?; - let full_viewing_key: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; + let full_viewing_key = FullViewingKey::decode(full_viewing_key)?; + + let action = build_action_inner(transaction_plan, action_plan, full_viewing_key, witness)?; + Ok(action.encode_to_vec()) +} + +pub fn build_action_inner( + transaction_plan: TransactionPlan, + action_plan: ActionPlan, + full_viewing_key: FullViewingKey, + witness: WitnessData, +) -> WasmResult { let memo_key = transaction_plan.memo.map(|memo_plan| memo_plan.key); let action = ActionPlan::build_unauth(action_plan, &full_viewing_key, &witness, memo_key)?; - Ok(action.encode_to_vec()) + + Ok(action) +} + +/// Build serial tx – +/// building a transaction may take some time, +/// depending on CPU performance and number of actions +/// in the transaction plan. +/// Arguments: +/// full_viewing_key: `FullViewingKey` +/// transaction_plan: `TransactionPlan` +/// witness_data: `WitnessData` +/// auth_data: `AuthorizationData` +/// Returns: `Transaction` +#[wasm_bindgen] +pub fn build_serial( + full_viewing_key: &[u8], + transaction_plan: &[u8], + witness_data: &[u8], + auth_data: &[u8], +) -> WasmResult> { + utils::set_panic_hook(); + + let plan = TransactionPlan::decode(transaction_plan)?; + let witness = WitnessData::decode(witness_data)?; + let auth = AuthorizationData::decode(auth_data)?; + let fvk = FullViewingKey::decode(full_viewing_key)?; + + let tx: Transaction = build_serial_inner(fvk, plan, witness, auth)?; + + Ok(tx.encode_to_vec()) +} + +pub fn build_serial_inner( + fvk: FullViewingKey, + plan: TransactionPlan, + witness: WitnessData, + auth: AuthorizationData, +) -> WasmResult { + let tx: Transaction = plan.build(&fvk, &witness, &auth)?; + + Ok(tx) +} + +/// Build parallel tx – +/// building a transaction may take some time, +/// depending on CPU performance and number of +/// actions in the transaction plan. +/// Arguments: +/// actions: `Vec` +/// transaction_plan: `TransactionPlan` +/// witness_data: `WitnessData` +/// auth_data: `AuthorizationData` +/// Returns: `Transaction` +#[wasm_bindgen] +pub fn build_parallel( + actions: JsValue, + transaction_plan: &[u8], + witness_data: &[u8], + auth_data: &[u8], +) -> WasmResult> { + utils::set_panic_hook(); + + let plan = TransactionPlan::decode(transaction_plan)?; + let witness = WitnessData::decode(witness_data)?; + let auth = AuthorizationData::decode(auth_data)?; + let actions: Vec = serde_wasm_bindgen::from_value(actions)?; + + let tx = build_parallel_inner(actions, plan, witness, auth)?; + + Ok(tx.encode_to_vec()) +} + +pub fn build_parallel_inner( + actions: Vec, + plan: TransactionPlan, + witness: WitnessData, + auth: AuthorizationData, +) -> WasmResult { + let transaction = plan.clone().build_unauth_with_actions(actions, &witness)?; + let tx = plan.apply_auth_data(&auth, transaction)?; + + Ok(tx) } diff --git a/packages/wasm/crate/src/database/indexed_db.rs b/packages/wasm/crate/src/database/indexed_db.rs index 9e40f283c6..a3e691a823 100644 --- a/packages/wasm/crate/src/database/indexed_db.rs +++ b/packages/wasm/crate/src/database/indexed_db.rs @@ -1,8 +1,7 @@ use std::future::IntoFuture; -use indexed_db_futures::idb_object_store::IdbObjectStoreParameters; -use indexed_db_futures::prelude::{IdbOpenDbRequestLike, OpenDbRequest}; -use indexed_db_futures::{IdbDatabase, IdbKeyPath, IdbQuerySource, IdbVersionChangeEvent}; +use indexed_db_futures::prelude::OpenDbRequest; +use indexed_db_futures::{IdbDatabase, IdbQuerySource}; use serde::de::DeserializeOwned; use serde::Serialize; use wasm_bindgen::JsValue; @@ -16,59 +15,10 @@ pub async fn open_idb_database(constants: &DbConstants) -> WasmResult OpenDbRequest { - db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> { - // Check if the object store exists; create it if it doesn't - if evt.db().name() == "penumbra-db-wasm-test" { - let note_key: JsValue = serde_wasm_bindgen::to_value("noteCommitment.inner")?; - let note_object_store_params = IdbObjectStoreParameters::new() - .key_path(Some(&IdbKeyPath::new(note_key))) - .to_owned(); - let note_object_store = evt - .db() - .create_object_store_with_params("SPENDABLE_NOTES", ¬e_object_store_params)?; - - let nullifier_key: JsValue = serde_wasm_bindgen::to_value("nullifier.inner")?; - let params = web_sys::IdbIndexParameters::new(); - params.set_unique(false); - note_object_store.create_index_with_params( - "nullifier", - &IdbKeyPath::new(nullifier_key), - ¶ms, - )?; - evt.db().create_object_store("TREE_LAST_POSITION")?; - evt.db().create_object_store("TREE_LAST_FORGOTTEN")?; - - let commitment_key: JsValue = serde_wasm_bindgen::to_value("commitment.inner")?; - let commitment_object_store_params = IdbObjectStoreParameters::new() - .key_path(Some(&IdbKeyPath::new(commitment_key))) - .to_owned(); - evt.db().create_object_store_with_params( - "TREE_COMMITMENTS", - &commitment_object_store_params, - )?; - evt.db().create_object_store("TREE_HASHES")?; - evt.db().create_object_store("FMD_PARAMETERS")?; - evt.db().create_object_store("APP_PARAMETERS")?; - evt.db().create_object_store("GAS_PRICES")?; - } - Ok(()) - })); - - db_req -} - impl Database for IdbDatabase { async fn get(&self, table: &str, key: K) -> WasmResult> where diff --git a/packages/wasm/crate/src/database/mock.rs b/packages/wasm/crate/src/database/mock.rs index a1dfce23c2..b541817eef 100644 --- a/packages/wasm/crate/src/database/mock.rs +++ b/packages/wasm/crate/src/database/mock.rs @@ -24,6 +24,9 @@ pub fn get_mock_tables() -> Tables { full_sync_height: "full_sync_height".to_string(), auctions: "auctions".to_string(), auction_outstanding_reserves: "auction_outstanding_reserves".to_string(), + tree_commitments: "tree_commitments".to_string(), + tree_last_position: "tree_last_position".to_string(), + tree_last_forgotten: "tree_last_forgotten".to_string(), } } diff --git a/packages/wasm/crate/src/storage.rs b/packages/wasm/crate/src/storage.rs index 4a8e0f1a7a..7f65026b5a 100644 --- a/packages/wasm/crate/src/storage.rs +++ b/packages/wasm/crate/src/storage.rs @@ -47,6 +47,9 @@ pub struct Tables { pub full_sync_height: String, pub auctions: String, pub auction_outstanding_reserves: String, + pub tree_commitments: String, + pub tree_last_position: String, + pub tree_last_forgotten: String, } pub async fn init_idb_storage(constants: DbConstants) -> WasmResult> { diff --git a/packages/wasm/crate/src/tx.rs b/packages/wasm/crate/src/tx.rs index 4f32280151..a4aa2738bf 100644 --- a/packages/wasm/crate/src/tx.rs +++ b/packages/wasm/crate/src/tx.rs @@ -112,64 +112,6 @@ pub fn witness(transaction_plan: &[u8], stored_tree: JsValue) -> WasmResult WasmResult> { - utils::set_panic_hook(); - - let plan = TransactionPlan::decode(transaction_plan)?; - let witness = WitnessData::decode(witness_data)?; - let auth = AuthorizationData::decode(auth_data)?; - let fvk: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; - - let tx: Transaction = plan.build(&fvk, &witness, &auth)?; - - Ok(tx.encode_to_vec()) -} - -/// Build parallel tx -/// Building a transaction may take some time, -/// depending on CPU performance and number of actions in transaction_plan -/// Arguments: -/// actions: `Vec` -/// transaction_plan: `pb::TransactionPlan` -/// witness_data: `pb::WitnessData` -/// auth_data: `pb::AuthorizationData` -/// Returns: `pb::Transaction` -#[wasm_bindgen] -pub fn build_parallel( - actions: JsValue, - transaction_plan: &[u8], - witness_data: &[u8], - auth_data: &[u8], -) -> WasmResult> { - utils::set_panic_hook(); - - let plan = TransactionPlan::decode(transaction_plan)?; - let witness = WitnessData::decode(witness_data)?; - let auth = AuthorizationData::decode(auth_data)?; - let actions: Vec = serde_wasm_bindgen::from_value(actions)?; - - let transaction = plan.clone().build_unauth_with_actions(actions, &witness)?; - - let tx = plan.apply_auth_data(&auth, transaction)?; - - Ok(tx.encode_to_vec()) -} - #[wasm_bindgen(getter_with_clone)] pub struct TxpAndTxvBytes { pub txp: Vec, diff --git a/packages/wasm/crate/tests/test_build.rs b/packages/wasm/crate/tests/test_build.rs index 2618fd2eb6..649fb57f73 100644 --- a/packages/wasm/crate/tests/test_build.rs +++ b/packages/wasm/crate/tests/test_build.rs @@ -1,577 +1,373 @@ extern crate penumbra_wasm; +mod utils; +use decaf377::Fq; +use penumbra_asset::asset::Id; +use penumbra_asset::{Value, STAKING_TOKEN_ASSET_ID}; +use penumbra_dex::DexParameters; +use penumbra_keys::keys::{AddressIndex, SpendKey}; +use penumbra_keys::FullViewingKey; +use penumbra_proto::core::app::v1::AppParameters; +use penumbra_proto::core::component::fee::v1::GasPrices; +use penumbra_proto::core::keys::v1::Address as AddressProto; +use penumbra_proto::view::v1::transaction_planner_request::Output; +use penumbra_proto::view::v1::TransactionPlannerRequest; +use penumbra_proto::DomainType; +use penumbra_sct::params::SctParameters; +use penumbra_sct::{CommitmentSource, Nullifier}; +use penumbra_shielded_pool::fmd::Parameters; +use penumbra_shielded_pool::{Note, Rseed}; +use penumbra_tct::{Forgotten, StateCommitment}; +use penumbra_transaction::{Action, ActionPlan, AuthorizationData, WitnessData}; +use penumbra_wasm::build::{build_action_inner, build_parallel_inner, build_serial_inner}; +use penumbra_wasm::database::interface::Database; +use penumbra_wasm::database::mock::{get_mock_tables, MockDb}; +use penumbra_wasm::note_record::SpendableNoteRecord; +use penumbra_wasm::planner::plan_transaction_inner; +use penumbra_wasm::storage::{byte_array_to_base64, Storage}; +use penumbra_wasm::{ + keys::load_proving_key, + tx::{authorize, witness}, +}; +use std::str::FromStr; +pub use utils::planner_setup::*; +pub use utils::sct::*; +use wasm_bindgen_test::*; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[wasm_bindgen_test] +async fn mock_build_serial_and_parallel() { + // Load the proving key parameters as byte arrays. + let spend_key: &[u8] = include_bytes!("../../../keys/keys/spend_pk.bin"); + let output_key: &[u8] = include_bytes!("../../../keys/keys/output_pk.bin"); + let delegator_vote_key: &[u8] = include_bytes!("../../../keys/keys/delegator_vote_pk.bin"); + let swap_key: &[u8] = include_bytes!("../../../keys/keys/swap_pk.bin"); + let swapclaim_key: &[u8] = include_bytes!("../../../keys/keys/swapclaim_pk.bin"); + let convert_key: &[u8] = include_bytes!("../../../keys/keys/convert_pk.bin"); + + // Dynamically load the proving keys at runtime for each key type. + load_proving_key(spend_key, "spend").expect("can load spend key"); + load_proving_key(output_key, "output").expect("can load output key"); + load_proving_key(delegator_vote_key, "delegatorVote").expect("can load delegator vote key"); + load_proving_key(swap_key, "swap").expect("can load swap key"); + load_proving_key(swapclaim_key, "swapClaim").expect("can load swapclaim key"); + load_proving_key(convert_key, "undelegateClaim").expect("can load convert key"); + + // MockDb + let mock_db = MockDb::new(); + let tables = get_mock_tables(); + + // Sample chain and fmd parameters. + let app_params = AppParameters { + dex_params: Some( + DexParameters { + fixed_candidates: Vec::new(), + is_enabled: true, + max_hops: 5u32, + max_positions_per_pair: 0, + max_execution_budget: 0u32, + } + .to_proto(), + ), + chain_id: "penumbra-testnet-iapetus".to_string(), + sct_params: Some( + SctParameters { + epoch_duration: 5u64, + } + .to_proto(), + ), + community_pool_params: None, + governance_params: None, + ibc_params: None, + stake_params: None, + fee_params: None, + distributions_params: None, + funding_params: None, + shielded_pool_params: None, + auction_params: None, + }; -#[cfg(test)] -mod tests { - use std::str::FromStr; + let fmd_params = Parameters { + precision: Default::default(), + as_of_block_height: 0, + }; - use indexed_db_futures::prelude::{ - IdbDatabase, IdbObjectStore, IdbQuerySource, IdbTransaction, IdbTransactionMode, + let gas_prices = GasPrices { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + block_space_price: 0, + compact_block_space_price: 0, + verification_price: 0, + execution_price: 0, }; - use penumbra_asset::STAKING_TOKEN_ASSET_ID; - use penumbra_dex::DexParameters; - use penumbra_keys::keys::SpendKey; - use penumbra_keys::FullViewingKey; - use penumbra_proto::core::app::v1::AppParameters; - use penumbra_proto::core::component::fee::v1::GasPrices; - use penumbra_proto::core::transaction::v1; - use penumbra_proto::view::v1::transaction_planner_request::Output; - use penumbra_proto::view::v1::TransactionPlannerRequest; - use penumbra_proto::{ - core::{ - asset::v1::Value, component::shielded_pool::v1::FmdParameters, keys::v1::Address, - transaction::v1::MemoPlaintext, + + // Decode the Bech32m string into the `Id` type + let decoded_id = + Id::from_str("passet1nupu8yg2kua09ec8qxfsl60xhafp7mmpsjv9pgp50t20hm6pkygscjcqn2") + .expect("failed to decode assetId"); + + // Convert the bytes into Fq field elements + #[allow(deprecated)] + let state_commitment = Fq::from_le_bytes_mod_order( + &base64::decode("MY7PmcrH4fhjFOoMIKEdF+x9EUhZ9CS/CIfVco7Y5wU=").expect("state commitment"), + ); + #[allow(deprecated)] + let nullifier = Fq::from_le_bytes_mod_order( + &base64::decode("8TvyFVKk16PHcOEAgl0QV4/92xdVpLdXI+zP87lBrQ8=").expect("nullifier"), + ); + #[allow(deprecated)] + let rseed: [u8; 32] = base64::decode("p2w4O1ognDJtKVqhHK2qsUbV+1AEM/gn58uWYQ5v3sM=") + .expect("rssed") + .try_into() + .expect("Invalid base64 decoding"); + + let spendable_note = SpendableNoteRecord { + note_commitment: StateCommitment(state_commitment), + note: Note::from_parts( + AddressProto{ inner: "".into(), alt_bech32m: "penumbra1z7j020uafn2s8ths6xsjjvcay57thkfsuyrksaxja0jjz3ch2hdzljd6hpju8vzyupld25fld2lzmpzqe576nsr35c82e0u9hgphc76ldxlt7amx4xfc636w9cnnasl9nl4u2j".to_string() }.try_into().expect("msg"), + Value { + amount: 1000000u64.into(), + asset_id: decoded_id, + }, + Rseed(rseed), + ) + .expect("note"), + address_index: AddressIndex { + account: 0, + randomizer: [0; 12], }, - view::v1::SpendableNoteRecord, - DomainType, - }; - use penumbra_sct::params::SctParameters; - use penumbra_tct::{structure::Hash, Forgotten}; - use penumbra_transaction::{ - plan::{ActionPlan, TransactionPlan}, - Action, Transaction, - }; - use prost::Message; - use serde::{Deserialize, Serialize}; - use wasm_bindgen::JsValue; - use wasm_bindgen_test::*; - - use penumbra_wasm::planner::plan_transaction; - use penumbra_wasm::storage::byte_array_to_base64; - use penumbra_wasm::storage::init_idb_storage; - use penumbra_wasm::{ - build::build_action, - keys::load_proving_key, - tx::{authorize, build, build_parallel, witness}, + nullifier: Nullifier(nullifier), + height_created: 250305, + height_spent: None, + position: 3204061134848.into(), + source: CommitmentSource::Transaction { + id: Some([ + 160, 159, 65, 163, 219, 246, 218, 202, 237, 82, 98, 157, 76, 3, 21, 192, 243, 174, + 26, 233, 150, 19, 103, 0, 184, 22, 217, 29, 200, 188, 7, 82, + ]), + }, + return_address: None, }; - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - #[wasm_bindgen_test] - async fn mock_build_serial_and_parallel() { - // Limit the use of Penumbra Rust libraries since we're mocking JS calls - // that are based on constructing objects according to protobuf definitions. - - // Load the proving key parameters as byte arrays. - let spend_key: &[u8] = include_bytes!("../../../keys/keys/spend_pk.bin"); - let output_key: &[u8] = include_bytes!("../../../keys/keys/output_pk.bin"); - let delegator_vote_key: &[u8] = include_bytes!("../../../keys/keys/delegator_vote_pk.bin"); - let swap_key: &[u8] = include_bytes!("../../../keys/keys/swap_pk.bin"); - let swapclaim_key: &[u8] = include_bytes!("../../../keys/keys/swapclaim_pk.bin"); - let convert_key: &[u8] = include_bytes!("../../../keys/keys/convert_pk.bin"); - - // Dynamically load the proving keys at runtime for each key type. - load_proving_key(spend_key, "spend").expect("can load spend key"); - load_proving_key(output_key, "output").expect("can load output key"); - load_proving_key(delegator_vote_key, "delegatorVote").expect("can load delegator vote key"); - load_proving_key(swap_key, "swap").expect("can load swap key"); - load_proving_key(swapclaim_key, "swapClaim").expect("can load swapclaim key"); - load_proving_key(convert_key, "undelegateClaim").expect("can load convert key"); - - // Define database parameters. - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct IndexedDbConstants { - name: String, - version: u32, - tables: Tables, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct Tables { - assets: String, - advice_notes: String, - spendable_notes: String, - swaps: String, - fmd_parameters: String, - app_parameters: String, - gas_prices: String, - epochs: String, - transactions: String, - full_sync_height: String, - auctions: String, - auction_outstanding_reserves: String, - } - - // Define `IndexDB` table parameters and constants. - let tables: Tables = Tables { - assets: "ASSETS".to_string(), - advice_notes: "ADVICE_NOTES".to_string(), - spendable_notes: "SPENDABLE_NOTES".to_string(), - swaps: "SWAPS".to_string(), - fmd_parameters: "FMD_PARAMETERS".to_string(), - app_parameters: "APP_PARAMETERS".to_string(), - gas_prices: "GAS_PRICES".to_string(), - epochs: "EPOCHS".to_string(), - transactions: "TRANSACTIONS".to_string(), - full_sync_height: "FULL_SYNC_HEIGHT".to_string(), - auctions: "AUCTIONS".to_string(), - auction_outstanding_reserves: "AUCTION_OUTSTANDING_RESERVES".to_string(), - }; - - let constants: IndexedDbConstants = IndexedDbConstants { - name: "penumbra-db-wasm-test".to_string(), - version: 1, - tables, - }; - - // Sample chain and fmd parameters. - let chain_id = "penumbra-testnet-iapetus".to_string(); - let sct_params = SctParameters { - epoch_duration: 5u64, - }; - let dex_params = DexParameters { - fixed_candidates: Vec::new(), - is_enabled: true, - max_hops: 5u32, - max_positions_per_pair: 0, - max_execution_budget: 0u32, - }; - - let app_params = AppParameters { - dex_params: Some(dex_params.to_proto()), - chain_id, - sct_params: Some(sct_params.to_proto()), - community_pool_params: None, - governance_params: None, - ibc_params: None, - stake_params: None, - fee_params: None, - distributions_params: None, - funding_params: None, - shielded_pool_params: None, - auction_params: None, - }; - - let fmd_params = FmdParameters { - precision_bits: 0u32, - as_of_block_height: 1u64, - }; - let gas_prices = GasPrices { - asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), - block_space_price: 0, - compact_block_space_price: 0, - verification_price: 0, - execution_price: 0, - }; - - // Serialize the parameters into `JsValue`. - let js_app_params_value: JsValue = serde_wasm_bindgen::to_value(&app_params).unwrap(); - let js_fmd_params_value: JsValue = serde_wasm_bindgen::to_value(&fmd_params).unwrap(); - let js_constants_params_value: JsValue = serde_wasm_bindgen::to_value(&constants).unwrap(); - let js_gas_prices_value: JsValue = serde_wasm_bindgen::to_value(&gas_prices).unwrap(); - - // Create spendable UTXO note in JSON format. - let spendable_note_json = r#" - { - "note_commitment": { - "inner": "MY7PmcrH4fhjFOoMIKEdF+x9EUhZ9CS/CIfVco7Y5wU=" - }, - "note": { - "value": { - "amount": { - "lo": "1000000", - "hi": "0" - }, - "asset_id": { - "inner": "nwPDkQq3OvLnBwGTD+nmv1Ifb2GEmFCgNHrU++9BsRE=", - "alt_bech32m": "", - "alt_base_denom": "" - } - }, - "rseed": "p2w4O1ognDJtKVqhHK2qsUbV+1AEM/gn58uWYQ5v3sM=", - "address": { - "inner": "F6T1P51M1QOu8NGhKTMdJTy72TDhB2h00uvlIUcXVdovybq4ZcOwROB+1VE/ar4thEDNPanAcaYOrL+FugN8e19pvr93ZqmTjUdOLic+w+U=", - "alt_bech32m": "" - } + // Define a sample SCT update. + #[allow(non_snake_case)] + let sctUpdates = SctUpdates { + store_commitments: StoreCommitment { + commitment: Commitment { + inner: "MY7PmcrH4fhjFOoMIKEdF+x9EUhZ9CS/CIfVco7Y5wU=".to_string(), }, - "address_index": { - "account": "0", - "randomizer": "AAAAAAAAAAAAAAAA" + position: Position { + epoch: 746u64, + block: 237u64, + commitment: 0u64, }, - "nullifier": { - "inner": "8TvyFVKk16PHcOEAgl0QV4/92xdVpLdXI+zP87lBrQ8=" - }, - "height_created": "250305", - "height_spent": "0", - "position": "3204061134848", - "source": { - "transaction": { - "id": "oJ9Bo9v22srtUmKdTAMVwPOuGumWE2cAuBbZHci8B1I=" - } - } - } - "#; - - // Convert note to `SpendableNoteRecord`. - let spendable_note: SpendableNoteRecord = - serde_json::from_str(spendable_note_json).unwrap(); - - // Define neccessary parameters to mock `TransactionPlannerRequest` in JSON format. - let address_json = r#" - { - "alt_bech32m": "penumbra1dugkjttfezh4gfkqs77377gnjlvmkkehusx6953udxeescc0qpgk6gqc0jmrsjq8xphzrg938843p0e63z09vt8lzzmef0q330e5njuwh4290n8pemcmx70sasym0lcjkstgzc", - "inner": "" - } - "#; - let value_json = r#" - { - "amount": { - "lo": "1", - "hi": "0" + }, + set_position: StoredPosition { + Position: Position { + epoch: 750u64, + block: 710u64, + commitment: 0u64, }, - "asset_id": { - "inner": "nwPDkQq3OvLnBwGTD+nmv1Ifb2GEmFCgNHrU++9BsRE=", - "alt_bech32m": "", - "alt_base_denom": "" - } - } - "#; - - // Convert fields to JsValue. - let address: Address = serde_json::from_str(address_json).unwrap(); - let value: Value = serde_json::from_str(value_json).unwrap(); - - // Add memo to plan. - let memo: MemoPlaintext = MemoPlaintext { - return_address: Some(address.clone()), - text: "sample memo".to_string(), - }; - - // Retrieve private database handle with public getters. - let constants = serde_wasm_bindgen::from_value(js_constants_params_value.clone()).unwrap(); - let storage = init_idb_storage(constants).await.unwrap(); - - // let storage_ref: &IndexedDBStorage = unsafe { &*storage }; - let database: *const IdbDatabase = storage.get_database(); - let database_ref: &IdbDatabase = unsafe { &*database }; - - // Define SCT-related structs. - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct Position { - epoch: u64, - block: u64, - commitment: u64, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - #[allow(non_snake_case)] - pub struct StoredPosition { - Position: Position, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct StoreHash { - position: Position, - height: u64, - hash: Hash, - essential: bool, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct StoreCommitment { - commitment: Commitment, - position: Position, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct Commitment { - inner: String, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct StateCommitmentTree { - last_position: Position, - last_forgotten: u64, - hashes: StoreHash, - commitments: StoreCommitment, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct SctUpdates { - store_commitments: StoreCommitment, - set_position: StoredPosition, - set_forgotten: u64, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct StoredTree { - last_position: Option, - last_forgotten: Option, - hashes: Vec, - commitments: Vec, - } + }, + set_forgotten: 3u64, + }; - // Define a sample SCT update. - #[allow(non_snake_case)] - let sctUpdates = SctUpdates { - store_commitments: StoreCommitment { - commitment: Commitment { - inner: "MY7PmcrH4fhjFOoMIKEdF+x9EUhZ9CS/CIfVco7Y5wU=".to_string(), - }, - position: Position { - epoch: 746u64, - block: 237u64, - commitment: 0u64, - }, - }, - set_position: StoredPosition { - Position: Position { - epoch: 750u64, - block: 710u64, - commitment: 0u64, - }, - }, - set_forgotten: 3u64, - }; + // Populate MockDB + mock_db + .put_with_key(&tables.spendable_notes, "spendable_note", &spendable_note) + .await + .unwrap(); - // Populate database with records (CRUD). - let tx_note: IdbTransaction = database_ref - .transaction_on_one_with_mode("SPENDABLE_NOTES", IdbTransactionMode::Readwrite) - .unwrap(); - let tx_tree_commitments: IdbTransaction = database_ref - .transaction_on_one_with_mode("TREE_COMMITMENTS", IdbTransactionMode::Readwrite) - .unwrap(); - let tx_tree_last_position: IdbTransaction = database_ref - .transaction_on_one_with_mode("TREE_LAST_POSITION", IdbTransactionMode::Readwrite) - .unwrap(); - let tx_tree_last_forgotten: IdbTransaction = database_ref - .transaction_on_one_with_mode("TREE_LAST_FORGOTTEN", IdbTransactionMode::Readwrite) - .unwrap(); - let tx_fmd: IdbTransaction = database_ref - .transaction_on_one_with_mode("FMD_PARAMETERS", IdbTransactionMode::Readwrite) - .unwrap(); - let tx_app: IdbTransaction = database_ref - .transaction_on_one_with_mode("APP_PARAMETERS", IdbTransactionMode::Readwrite) - .unwrap(); - let tx_gas: IdbTransaction = database_ref - .transaction_on_one_with_mode("GAS_PRICES", IdbTransactionMode::Readwrite) - .unwrap(); + mock_db + .put_with_key( + &tables.tree_commitments, + "tree_commitments", + &sctUpdates.store_commitments, + ) + .await + .unwrap(); - let store_note: IdbObjectStore = tx_note.object_store("SPENDABLE_NOTES").unwrap(); - let store_tree_commitments: IdbObjectStore = tx_tree_commitments - .object_store("TREE_COMMITMENTS") - .unwrap(); - let store_tree_last_position: IdbObjectStore = tx_tree_last_position - .object_store("TREE_LAST_POSITION") - .unwrap(); - let store_tree_last_forgotten: IdbObjectStore = tx_tree_last_forgotten - .object_store("TREE_LAST_FORGOTTEN") - .unwrap(); - let store_fmd: IdbObjectStore = tx_fmd.object_store("FMD_PARAMETERS").unwrap(); - let store_app: IdbObjectStore = tx_app.object_store("APP_PARAMETERS").unwrap(); - let store_gas: IdbObjectStore = tx_gas.object_store("GAS_PRICES").unwrap(); - - let spendable_note_json = serde_wasm_bindgen::to_value(&spendable_note).unwrap(); - let tree_commitments_json = - serde_wasm_bindgen::to_value(&sctUpdates.store_commitments).unwrap(); - let tree_position_json_value = - serde_wasm_bindgen::to_value(&sctUpdates.set_position).unwrap(); - let tree_position_json_key = serde_wasm_bindgen::to_value(&"last_position").unwrap(); - let tree_last_forgotten_json_value = - serde_wasm_bindgen::to_value(&sctUpdates.set_forgotten).unwrap(); - let tree_last_forgotten_json_key: JsValue = - serde_wasm_bindgen::to_value(&"last_forgotten").unwrap(); - let fmd_json_key: JsValue = serde_wasm_bindgen::to_value(&"params").unwrap(); - let app_json_key: JsValue = serde_wasm_bindgen::to_value(&"params").unwrap(); - let gas_json_key: JsValue = JsValue::from_str(&byte_array_to_base64( - &STAKING_TOKEN_ASSET_ID.to_proto().inner, - )); - - store_note.put_val(&spendable_note_json).unwrap(); - store_tree_commitments - .put_val(&tree_commitments_json) - .unwrap(); - store_tree_last_position - .put_key_val(&tree_position_json_key, &tree_position_json_value) - .unwrap(); - store_tree_last_forgotten - .put_key_val( - &tree_last_forgotten_json_key, - &tree_last_forgotten_json_value, - ) - .unwrap(); - store_fmd - .put_key_val(&fmd_json_key, &js_fmd_params_value) - .unwrap(); - store_app - .put_key_val(&app_json_key, &js_app_params_value) - .unwrap(); - store_gas - .put_key_val(&gas_json_key, &js_gas_prices_value) - .unwrap(); + mock_db + .put_with_key( + &tables.tree_last_position, + "last_position", + &sctUpdates.set_position, + ) + .await + .unwrap(); - // -------------- 1. Query transaction plan performing a spend -------------- - - #[allow(deprecated)] // Remove if/when `epoch_index` is removed - let planner_request = TransactionPlannerRequest { - epoch: None, - epoch_index: 0, - expiry_height: 0, - memo: Some(memo), - source: None, - outputs: vec![Output { - address: Some(address), - value: Some(value), - }], - spends: vec![], - swaps: vec![], - swap_claims: vec![], - delegations: vec![], - undelegations: vec![], - undelegation_claims: vec![], - ibc_relay_actions: vec![], - ics20_withdrawals: vec![], - position_opens: vec![], - position_closes: vec![], - position_withdraws: vec![], - fee_mode: None, - dutch_auction_schedule_actions: vec![], - dutch_auction_end_actions: vec![], - dutch_auction_withdraw_actions: vec![], - delegator_votes: vec![], - }; - - // Viewing key to reveal asset balances and transactions. - let full_viewing_key = FullViewingKey::from_str("penumbrafullviewingkey1mnm04x7yx5tyznswlp0sxs8nsxtgxr9p98dp0msuek8fzxuknuzawjpct8zdevcvm3tsph0wvsuw33x2q42e7sf29q904hwerma8xzgrxsgq2").unwrap(); - - let plan_js_value: JsValue = plan_transaction( - js_constants_params_value, - &planner_request.encode_to_vec(), - full_viewing_key.encode_to_vec().as_slice(), - STAKING_TOKEN_ASSET_ID.encode_to_vec().as_slice(), + mock_db + .put_with_key( + &tables.tree_last_forgotten, + "last_forgotten", + &sctUpdates.set_forgotten, ) .await .unwrap(); - let plan_proto: v1::TransactionPlan = - serde_wasm_bindgen::from_value(plan_js_value.clone()).unwrap(); - let plan = TransactionPlan::try_from(plan_proto).unwrap(); - let plan_slice = &*plan.encode_to_vec(); - // -------------- 2. Generate authorization data from spend key and transaction plan -------------- + mock_db + .put_with_key(&tables.fmd_parameters, "params", &fmd_params) + .await + .unwrap(); - let spend_key = SpendKey::from_str( - "penumbraspendkey1qul0huewkcmemljd5m3vz3awqt7442tjg2dudahvzu6eyj9qf0eszrnguh", + mock_db + .put_with_key(&tables.clone().app_parameters, "params", &app_params) + .await + .unwrap(); + + mock_db + .put_with_key( + &tables.clone().gas_prices, + byte_array_to_base64(&STAKING_TOKEN_ASSET_ID.to_proto().inner), + &gas_prices, ) + .await .unwrap(); - let authorization_data = - authorize(spend_key.encode_to_vec().as_slice(), plan_slice).unwrap(); + // Instantiate storage object + let storage: Storage = Storage::new(mock_db.clone(), tables.clone()).unwrap(); + + // 1. Query transaction plan performing a spend. + + #[allow(deprecated)] // Remove if/when `epoch_index` is removed + let planner_request = TransactionPlannerRequest { + epoch: None, + epoch_index: 0, + expiry_height: 0, + memo: None, + source: None, + outputs: vec![Output { + address: Some(AddressProto { inner: "".into(), alt_bech32m: "penumbra1dugkjttfezh4gfkqs77377gnjlvmkkehusx6953udxeescc0qpgk6gqc0jmrsjq8xphzrg938843p0e63z09vt8lzzmef0q330e5njuwh4290n8pemcmx70sasym0lcjkstgzc".to_string() }), + value: Some(Value { + amount: 1u64.into(), + asset_id: decoded_id, + }.into()), + }], + spends: vec![], + swaps: vec![], + swap_claims: vec![], + delegations: vec![], + undelegations: vec![], + undelegation_claims: vec![], + ibc_relay_actions: vec![], + ics20_withdrawals: vec![], + position_opens: vec![], + position_closes: vec![], + position_withdraws: vec![], + fee_mode: None, + dutch_auction_schedule_actions: vec![], + dutch_auction_end_actions: vec![], + dutch_auction_withdraw_actions: vec![], + delegator_votes: vec![], + }; - // -------------- 3. Generate witness -------------- + // Viewing key to reveal asset balances and transactions. + let full_viewing_key = FullViewingKey::from_str("penumbrafullviewingkey1mnm04x7yx5tyznswlp0sxs8nsxtgxr9p98dp0msuek8fzxuknuzawjpct8zdevcvm3tsph0wvsuw33x2q42e7sf29q904hwerma8xzgrxsgq2").unwrap(); - // Retrieve SCT from storage. - let tx_last_position: IdbTransaction<'_> = database_ref - .transaction_on_one("TREE_LAST_POSITION") - .unwrap(); - let store_last_position = tx_last_position.object_store("TREE_LAST_POSITION").unwrap(); - let value_last_position: Option = store_last_position - .get_owned("last_position") - .unwrap() - .await - .unwrap(); + let transaction_plan = plan_transaction_inner( + storage.clone(), + planner_request, + full_viewing_key.clone(), + *STAKING_TOKEN_ASSET_ID, + ) + .await + .unwrap(); - let tx_last_forgotten = database_ref - .transaction_on_one("TREE_LAST_FORGOTTEN") - .unwrap(); - let store_last_forgotten = tx_last_forgotten - .object_store("TREE_LAST_FORGOTTEN") - .unwrap(); - let value_last_forgotten: Option = store_last_forgotten - .get_owned("last_forgotten") - .unwrap() - .await - .unwrap(); + let plan_slice = &*transaction_plan.clone().encode_to_vec(); - let tx_commitments = database_ref.transaction_on_one("TREE_COMMITMENTS").unwrap(); - let store_commitments = tx_commitments.object_store("TREE_COMMITMENTS").unwrap(); - let value_commitments = store_commitments - .get_owned("MY7PmcrH4fhjFOoMIKEdF+x9EUhZ9CS/CIfVco7Y5wU=") - .unwrap() - .await - .unwrap(); + // 2. Generate authorization data from spend key and transaction plan. - // Convert retrieved values to `JsValue`. - let last_position_json: StoredPosition = - serde_wasm_bindgen::from_value(value_last_position.unwrap()).unwrap(); - let last_forgotten_json: Forgotten = - serde_wasm_bindgen::from_value(value_last_forgotten.unwrap()).unwrap(); - let commitments_jsvalue: StoreCommitment = - serde_wasm_bindgen::from_value(JsValue::from(value_commitments.clone())).unwrap(); - - // Reconstruct SCT struct. - let vec_store_commitments: Vec = vec![commitments_jsvalue.clone()]; - - let sct = StoredTree { - last_position: Some(last_position_json.clone()), - last_forgotten: Some(last_forgotten_json), - hashes: [].to_vec(), - commitments: vec_store_commitments, - }; - - // Convert SCT to `JsValue`. - let sct_json = serde_wasm_bindgen::to_value(&sct).unwrap(); - - // Generate witness data from SCT and specific transaction plan. - let witness_data = witness(plan_slice, sct_json).unwrap(); - - // -------------- 4. Build the (1) Serial Transaction and (2) Parallel Transaction -------------- - - let mut actions: Vec = Vec::new(); - - for i in plan.actions.clone() { - if let ActionPlan::Spend(ref _spend_plan) = i { - let action_vec = build_action( - plan_slice, - i.encode_to_vec().as_slice(), - full_viewing_key.encode_to_vec().as_slice(), - &witness_data, - ) - .unwrap(); - let action = Action::decode(&*action_vec).unwrap(); - actions.push(action); - } - if let ActionPlan::Output(ref _output_plan) = i { - let action_vec = build_action( - plan_slice, - i.encode_to_vec().as_slice(), - full_viewing_key.encode_to_vec().as_slice(), - &witness_data, - ) - .unwrap(); - let action = Action::decode(&*action_vec).unwrap(); - actions.push(action); - } - } + let spend_key = SpendKey::from_str( + "penumbraspendkey1qul0huewkcmemljd5m3vz3awqt7442tjg2dudahvzu6eyj9qf0eszrnguh", + ) + .unwrap(); - // Deserialize actions. - let action_deserialized: JsValue = serde_wasm_bindgen::to_value(&actions).unwrap(); + let authorization_data_bytes: Vec = + authorize(spend_key.encode_to_vec().as_slice(), plan_slice).unwrap(); + let authorization_data = AuthorizationData::decode(&*authorization_data_bytes).unwrap(); - // Execute parallel spend transaction and generate proof. - let parallel_transaction = build_parallel( - action_deserialized, - plan_slice, - &witness_data, - &authorization_data, - ) + // 3. Generate witness. + + // Retrieve SCT from storage. + let tx_last_position: StoredPosition = mock_db + .get(&tables.tree_last_position, "last_position") + .await + .unwrap() .unwrap(); - console_log!("Parallel transaction is: {:?}", parallel_transaction); - - // Execute serial spend transaction and generate proof. - let serial_transaction = build( - full_viewing_key.encode_to_vec().as_slice(), - plan_slice, - &witness_data, - &authorization_data, - ) + let tx_last_forgotten: Forgotten = mock_db + .get(&tables.tree_last_forgotten, "last_forgotten") + .await + .unwrap() .unwrap(); - console_log!("Serial transaction is: {:?}", serial_transaction); + let tx_tree_commitments: StoreCommitment = mock_db + .get(&tables.tree_commitments, "tree_commitments") + .await + .unwrap() + .unwrap(); + + // Reconstruct SCT struct. + let vec_store_commitments: Vec = vec![tx_tree_commitments.clone()]; + let sct = StoredTree { + last_position: Some(tx_last_position.clone()), + last_forgotten: Some(tx_last_forgotten), + hashes: [].to_vec(), + commitments: vec_store_commitments, + }; - // Deserialize transactions and stringify actions in the transaction body into JSON - let serial_result = Transaction::decode(&*serial_transaction).unwrap(); - let parallel_result = Transaction::decode(&*parallel_transaction).unwrap(); - let serial_json = serde_json::to_string(&serial_result.transaction_body.actions).unwrap(); - let parallel_json = - serde_json::to_string(¶llel_result.transaction_body.actions).unwrap(); + // Convert SCT to `JsValue`. + let sct_json = serde_wasm_bindgen::to_value(&sct).unwrap(); - // Perform assertion check - assert_eq!(serial_json, parallel_json); + // Generate witness data from SCT and specific transaction plan. + let witness_data_bytes: Vec = witness(plan_slice, sct_json).unwrap(); + let witness_data: WitnessData = WitnessData::decode(&*witness_data_bytes).unwrap(); + + // 4. Build the serial and parallel transactions + + let mut actions: Vec = Vec::new(); + for i in transaction_plan.clone().actions.clone() { + if let ActionPlan::Spend(ref _spend_plan) = i { + let action = build_action_inner( + transaction_plan.clone(), + i.clone(), + full_viewing_key.clone(), + witness_data.clone(), + ) + .unwrap(); + actions.push(action); + } + if let ActionPlan::Output(ref _output_plan) = i { + let action = build_action_inner( + transaction_plan.clone(), + i, + full_viewing_key.clone(), + witness_data.clone(), + ) + .unwrap(); + actions.push(action); + } } + + // Execute parallel spend transaction and generate proof. + let parallel_transaction = build_parallel_inner( + actions, + transaction_plan.clone(), + witness_data.clone(), + authorization_data.clone(), + ) + .unwrap(); + + // Execute serial spend transaction and generate proof. + let serial_transaction = build_serial_inner( + full_viewing_key, + transaction_plan, + witness_data, + authorization_data, + ) + .unwrap(); + + // Deserialize transactions and stringify actions in the transaction body into JSON + let serial_json = serde_json::to_string(&serial_transaction.transaction_body.actions).unwrap(); + let parallel_json = + serde_json::to_string(¶llel_transaction.transaction_body.actions).unwrap(); + + // Perform assertion check + assert_eq!(serial_json, parallel_json); } diff --git a/packages/wasm/crate/tests/utils/mod.rs b/packages/wasm/crate/tests/utils/mod.rs index d14eb19272..64864a940a 100644 --- a/packages/wasm/crate/tests/utils/mod.rs +++ b/packages/wasm/crate/tests/utils/mod.rs @@ -1 +1,2 @@ pub mod planner_setup; +pub mod sct; diff --git a/packages/wasm/crate/tests/utils/sct.rs b/packages/wasm/crate/tests/utils/sct.rs new file mode 100644 index 0000000000..4eff21ae8a --- /dev/null +++ b/packages/wasm/crate/tests/utils/sct.rs @@ -0,0 +1,58 @@ +use penumbra_tct::{structure::Hash, Forgotten}; +use serde::{Deserialize, Serialize}; + +// Define utility SCT-related structs. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Position { + pub epoch: u64, + pub block: u64, + pub commitment: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct StoredPosition { + pub Position: Position, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StoreHash { + pub position: Position, + pub height: u64, + pub hash: Hash, + pub essential: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StoreCommitment { + pub commitment: Commitment, + pub position: Position, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Commitment { + pub inner: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StateCommitmentTree { + pub last_position: Position, + pub last_forgotten: u64, + pub hashes: StoreHash, + pub commitments: StoreCommitment, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SctUpdates { + pub store_commitments: StoreCommitment, + pub set_position: StoredPosition, + pub set_forgotten: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StoredTree { + pub last_position: Option, + pub last_forgotten: Option, + pub hashes: Vec, + pub commitments: Vec, +} diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 7d24b73938..330dbf1b36 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -21,7 +21,7 @@ "postcompile": "touch ./wasm/.npmignore", "test": "vitest run", "test:cargo": "cd crate && cargo test --release", - "test:wasm": "cd crate && wasm-pack test --headless --firefox -- --target wasm32-unknown-unknown --release --features 'mock-database'" + "test:wasm": "cd crate && wasm-pack test --headless --firefox -- --target wasm32-unknown-unknown --release" }, "files": [ "wasm",