diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index c71aa5cd80..e1d20304b5 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -1,7 +1,6 @@ import { RootQuerier } from './root-querier'; import { sha256Hash } from '@penumbra-zone/crypto-web/src/sha256'; import { computePositionId, getLpNftMetadata } from '@penumbra-zone/wasm/src/dex'; -import { decodeSctRoot } from '@penumbra-zone/wasm/src/sct'; import { getExchangeRateFromValidatorInfoResponse, @@ -385,14 +384,15 @@ export class BlockProcessor implements BlockProcessorInterface { } } - // Compares the locally stored, filtered SCT root with the actual one on chain. They should match. - // This is expensive to do every block, so should only be done in development. + // Compares the locally stored, filtered TCT root with the actual one on chain. They should match. + // This is expensive to do every block, so should only be done in development for debugging purposes. + // Recommend putting it alongside a flush (when flushReasons are triggered). // @ts-expect-error Only used ad-hoc in dev private async assertRootValid(blockHeight: bigint): Promise { - const sourceOfTruth = await this.querier.cnidarium.keyValue(`sct/anchor/${blockHeight}`); + const remoteRoot = await this.querier.cnidarium.fetchRemoteRoot(blockHeight); const inMemoryRoot = this.viewServer.getSctRoot(); - if (!decodeSctRoot(sourceOfTruth).equals(inMemoryRoot)) { + if (!remoteRoot.equals(inMemoryRoot)) { throw new Error( `Block height: ${blockHeight}. Wasm root does not match remote source of truth. Programmer error.`, ); diff --git a/packages/query/src/queriers/cnidarium.ts b/packages/query/src/queriers/cnidarium.ts index 8925aba7be..f8a9dc33a9 100644 --- a/packages/query/src/queriers/cnidarium.ts +++ b/packages/query/src/queriers/cnidarium.ts @@ -1,12 +1,9 @@ import { PromiseClient } from '@connectrpc/connect'; import { createClient } from './utils'; import { QueryService as CnidariumQueryService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/cnidarium/v1/cnidarium_connect'; - -import { - KeyValueRequest, - KeyValueResponse_Value, -} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/cnidarium/v1/cnidarium_pb'; +import { KeyValueRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/cnidarium/v1/cnidarium_pb'; import { CnidariumQuerierInterface } from '@penumbra-zone/types/src/querier'; +import { MerkleRoot } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb'; export class CnidariumQuerier implements CnidariumQuerierInterface { private readonly client: PromiseClient; @@ -15,9 +12,13 @@ export class CnidariumQuerier implements CnidariumQuerierInterface { this.client = createClient(grpcEndpoint, CnidariumQueryService); } - async keyValue(key: string): Promise { - const keyValueRequest = new KeyValueRequest({ key }); + async fetchRemoteRoot(blockHeight: bigint): Promise { + const keyValueRequest = new KeyValueRequest({ + key: `sct/tree/anchor_by_height/${blockHeight}`, + }); const keyValue = await this.client.keyValue(keyValueRequest); - return keyValue.value!.value; + if (!keyValue.value) throw new Error('no value in KeyValueResponse'); + + return MerkleRoot.fromBinary(keyValue.value.value); } } diff --git a/packages/types/src/querier.ts b/packages/types/src/querier.ts index 9f3fb45500..7a57467596 100644 --- a/packages/types/src/querier.ts +++ b/packages/types/src/querier.ts @@ -8,7 +8,6 @@ import { QueryClientStatesRequest, QueryClientStatesResponse, } from '@buf/cosmos_ibc.bufbuild_es/ibc/core/client/v1/query_pb'; -import { KeyValueResponse_Value } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/cnidarium/v1/cnidarium_pb'; import { TransactionId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/txhash/v1/txhash_pb'; import { Transaction } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; import { @@ -17,6 +16,7 @@ import { ValidatorPenaltyRequest, ValidatorPenaltyResponse, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb'; +import { MerkleRoot } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb'; export interface RootQuerierInterface { app: AppQuerierInterface; @@ -63,5 +63,5 @@ export interface StakingQuerierInterface { } export interface CnidariumQuerierInterface { - keyValue(key: string): Promise; + fetchRemoteRoot(blockHeight: bigint): Promise; } diff --git a/packages/wasm/crate/Cargo.lock b/packages/wasm/crate/Cargo.lock index 4855aae183..1e732cfe36 100644 --- a/packages/wasm/crate/Cargo.lock +++ b/packages/wasm/crate/Cargo.lock @@ -2726,7 +2726,7 @@ dependencies = [ [[package]] name = "penumbra-wasm" -version = "0.1.0" +version = "2.0.0" dependencies = [ "anyhow", "ark-ff", @@ -2748,6 +2748,7 @@ dependencies = [ "penumbra-stake", "penumbra-tct", "penumbra-transaction", + "prost", "rand_core", "serde", "serde-wasm-bindgen", diff --git a/packages/wasm/crate/Cargo.toml b/packages/wasm/crate/Cargo.toml index e0e6358ef2..37c0afedf6 100644 --- a/packages/wasm/crate/Cargo.toml +++ b/packages/wasm/crate/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "penumbra-wasm" -version = "0.1.0" +version = "2.0.0" edition = "2021" [profile.release] @@ -36,6 +36,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true } decaf377 = { version = "0.5.0", features = ["r1cs"] } hex = "0.4.3" indexed_db_futures = "0.4.1" +prost = "0.12.3" rand_core = { version = "0.6.4", features = ["getrandom"] } serde = { version = "1.0.197", features = ["derive"] } serde-wasm-bindgen = "0.6.5" diff --git a/packages/wasm/crate/src/build.rs b/packages/wasm/crate/src/build.rs index a245230512..39e4aa2344 100644 --- a/packages/wasm/crate/src/build.rs +++ b/packages/wasm/crate/src/build.rs @@ -5,7 +5,6 @@ use penumbra_transaction::{ WitnessData, }; use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; use crate::error::WasmResult; use crate::utils; @@ -20,26 +19,19 @@ use crate::utils; /// Returns: `Action` #[wasm_bindgen] pub fn build_action( - transaction_plan: JsValue, - action_plan: JsValue, + transaction_plan: &[u8], + action_plan: &[u8], full_viewing_key: &[u8], - witness_data: JsValue, -) -> WasmResult { + witness_data: &[u8], +) -> WasmResult> { utils::set_panic_hook(); - - let transaction_plan: TransactionPlan = - serde_wasm_bindgen::from_value(transaction_plan.clone())?; - - let witness: WitnessData = serde_wasm_bindgen::from_value(witness_data)?; - - let action_plan: ActionPlan = serde_wasm_bindgen::from_value(action_plan)?; - + 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 memo_key = transaction_plan.memo.map(|memo_plan| memo_plan.key); let action = ActionPlan::build_unauth(action_plan, &full_viewing_key, &witness, memo_key)?; - - let result = serde_wasm_bindgen::to_value(&action)?; - Ok(result) + Ok(action.encode_to_vec()) } diff --git a/packages/wasm/crate/src/dex.rs b/packages/wasm/crate/src/dex.rs index fa806fbdaf..9de3c6e906 100644 --- a/packages/wasm/crate/src/dex.rs +++ b/packages/wasm/crate/src/dex.rs @@ -1,36 +1,34 @@ -use crate::utils; -use penumbra_dex::lp::position::{Id, Position}; +use penumbra_dex::lp::position::{Id, Position, State}; use penumbra_dex::lp::LpNft; -use serde_wasm_bindgen::Error; +use penumbra_proto::DomainType; use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; + +use crate::error::WasmResult; +use crate::utils; /// compute position id /// Arguments: -/// position: `Position` -/// Returns: `PositionId` +/// position: `Uint8Array representing a Position` +/// Returns: ` Uint8Array representing a PositionId` #[wasm_bindgen] -pub fn compute_position_id(position: JsValue) -> Result { +pub fn compute_position_id(position: &[u8]) -> WasmResult> { utils::set_panic_hook(); - let position: Position = serde_wasm_bindgen::from_value(position)?; - serde_wasm_bindgen::to_value(&position.id()) + let position = Position::decode(position)?; + Ok(position.id().encode_to_vec()) } /// get LP NFT asset /// Arguments: /// position_value: `lp::position::Position` -/// position_state_value: `lp::position::State` -/// Returns: `DenomMetadata` +/// position_state: `lp::position::State` +/// Returns: `Uint8Array representing a DenomMetadata` #[wasm_bindgen] -pub fn get_lpnft_asset( - position_id_value: JsValue, - position_state_value: JsValue, -) -> Result { +pub fn get_lpnft_asset(position_id: &[u8], position_state: &[u8]) -> WasmResult> { utils::set_panic_hook(); - let position_id: Id = serde_wasm_bindgen::from_value(position_id_value)?; - let position_state = serde_wasm_bindgen::from_value(position_state_value)?; + let position_id = Id::decode(position_id)?; + let position_state = State::decode(position_state)?; let lp_nft = LpNft::new(position_id, position_state); let denom = lp_nft.denom(); - serde_wasm_bindgen::to_value(&denom) + Ok(denom.encode_to_vec()) } diff --git a/packages/wasm/crate/src/error.rs b/packages/wasm/crate/src/error.rs index 5370f3e1a3..247e663918 100644 --- a/packages/wasm/crate/src/error.rs +++ b/packages/wasm/crate/src/error.rs @@ -1,14 +1,14 @@ use std::convert::Infallible; -use base64::DecodeError; +use base64::DecodeError as Base64DecodeError; use hex::FromHexError; +use penumbra_tct::error::{InsertBlockError, InsertEpochError, InsertError}; +use prost::DecodeError as ProstDecodeError; use serde_wasm_bindgen::Error; use thiserror::Error; use wasm_bindgen::{JsError, JsValue}; use web_sys::DomException; -use penumbra_tct::error::{InsertBlockError, InsertEpochError, InsertError}; - pub type WasmResult = Result; #[derive(Error, Debug)] @@ -17,7 +17,7 @@ pub enum WasmError { Anyhow(#[from] anyhow::Error), #[error("{0}")] - DecodeError(#[from] DecodeError), + Base64DecodeError(#[from] Base64DecodeError), #[error("{0}")] Dom(#[from] DomError), @@ -37,6 +37,9 @@ pub enum WasmError { #[error("{0}")] InsertError(#[from] InsertError), + #[error("Decode error: {0}")] + ProstDecodeError(#[from] ProstDecodeError), + #[error("{0}")] Wasm(#[from] serde_wasm_bindgen::Error), } diff --git a/packages/wasm/crate/src/keys.rs b/packages/wasm/crate/src/keys.rs index 155b4563df..9fc3cbdb3e 100644 --- a/packages/wasm/crate/src/keys.rs +++ b/packages/wasm/crate/src/keys.rs @@ -12,7 +12,6 @@ use penumbra_proto::core::keys::v1::WalletId; use penumbra_proto::{DomainType, Message}; use rand_core::OsRng; use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::js_sys::Uint8Array; use crate::error::WasmResult; use crate::utils; @@ -23,10 +22,7 @@ use crate::utils; /// function will additionally require downloading the proving key parameter `.bin` /// file for each key type. #[wasm_bindgen] -pub fn load_proving_key(parameters: JsValue, key_type: &str) -> WasmResult<()> { - // Deserialize JsValue into Vec. - let parameters_bytes: Vec = Uint8Array::new(¶meters).to_vec(); - +pub fn load_proving_key(key: &[u8], key_type: &str) -> WasmResult<()> { // Map key type with proving keys. let proving_key_map = match key_type { "spend" => &SPEND_PROOF_PROVING_KEY, @@ -39,7 +35,7 @@ pub fn load_proving_key(parameters: JsValue, key_type: &str) -> WasmResult<()> { }; // Load proving key. - proving_key_map.try_load_unchecked(¶meters_bytes)?; + proving_key_map.try_load_unchecked(key)?; Ok(()) } diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 2e708ff518..3bac8fcbe2 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -26,6 +26,7 @@ use penumbra_stake::{IdentityKey, Penalty, 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; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -157,12 +158,12 @@ fn prioritize_and_filter_spendable_notes( #[wasm_bindgen] pub async fn plan_transaction( idb_constants: JsValue, - request: JsValue, + request: &[u8], full_viewing_key: &[u8], ) -> WasmResult { utils::set_panic_hook(); - let request: TransactionPlannerRequest = serde_wasm_bindgen::from_value(request)?; + let request = TransactionPlannerRequest::decode(request)?; let source_address_index: AddressIndex = request .source diff --git a/packages/wasm/crate/src/tx.rs b/packages/wasm/crate/src/tx.rs index d7329cf6cf..831c7e16a7 100644 --- a/packages/wasm/crate/src/tx.rs +++ b/packages/wasm/crate/src/tx.rs @@ -5,7 +5,6 @@ use anyhow::anyhow; use penumbra_dex::BatchSwapOutputData; use penumbra_keys::keys::SpendKey; use penumbra_keys::FullViewingKey; -use penumbra_proto::core::transaction::v1 as pb; use penumbra_proto::core::transaction::v1::{TransactionPerspective, TransactionView}; use penumbra_proto::DomainType; use penumbra_sct::{CommitmentSource, Nullifier}; @@ -37,50 +36,20 @@ impl TxInfoResponse { } } -/// encode transaction to bytes -/// Arguments: -/// transaction: `penumbra_transaction::Transaction` -/// Returns: `` -#[wasm_bindgen] -pub fn encode_tx(transaction: JsValue) -> WasmResult { - utils::set_panic_hook(); - - let tx: Transaction = serde_wasm_bindgen::from_value(transaction)?; - let result = serde_wasm_bindgen::to_value::>(&tx.into())?; - Ok(result) -} - -/// decode base64 bytes to transaction -/// Arguments: -/// tx_bytes: `base64 String` -/// Returns: `penumbra_transaction::Transaction` -#[wasm_bindgen] -pub fn decode_tx(tx_bytes: &str) -> WasmResult { - utils::set_panic_hook(); - - let tx_vec: Vec = - base64::Engine::decode(&base64::engine::general_purpose::STANDARD, tx_bytes)?; - let transaction: Transaction = Transaction::try_from(tx_vec)?; - let result = serde_wasm_bindgen::to_value(&transaction)?; - Ok(result) -} - /// authorize transaction (sign transaction using spend key) /// Arguments: /// spend_key: `byte representation inner SpendKey` /// transaction_plan: `pb::TransactionPlan` /// Returns: `pb::AuthorizationData` #[wasm_bindgen] -pub fn authorize(spend_key: &[u8], transaction_plan: JsValue) -> WasmResult { +pub fn authorize(spend_key: &[u8], transaction_plan: &[u8]) -> WasmResult> { utils::set_panic_hook(); - let plan_proto: pb::TransactionPlan = serde_wasm_bindgen::from_value(transaction_plan)?; let spend_key: SpendKey = SpendKey::decode(spend_key)?; - let plan: TransactionPlan = plan_proto.try_into()?; + let plan = TransactionPlan::decode(transaction_plan)?; let auth_data: AuthorizationData = plan.authorize(OsRng, &spend_key)?; - let result = serde_wasm_bindgen::to_value(&auth_data.to_proto())?; - Ok(result) + Ok(auth_data.encode_to_vec()) } /// Get witness data @@ -90,15 +59,11 @@ pub fn authorize(spend_key: &[u8], transaction_plan: JsValue) -> WasmResult WasmResult { +pub fn witness(transaction_plan: &[u8], stored_tree: JsValue) -> WasmResult> { utils::set_panic_hook(); - let plan_proto: pb::TransactionPlan = serde_wasm_bindgen::from_value(transaction_plan)?; - - let plan: TransactionPlan = plan_proto.try_into()?; - + let plan = TransactionPlan::decode(transaction_plan)?; let stored_tree: StoredTree = serde_wasm_bindgen::from_value(stored_tree)?; - let sct = load_tree(stored_tree); let note_commitments: Vec = plan @@ -144,8 +109,7 @@ pub fn witness(transaction_plan: JsValue, stored_tree: JsValue) -> WasmResult WasmResult WasmResult { + transaction_plan: &[u8], + witness_data: &[u8], + auth_data: &[u8], +) -> WasmResult> { utils::set_panic_hook(); - let plan_proto: pb::TransactionPlan = serde_wasm_bindgen::from_value(transaction_plan)?; - let witness_data_proto: pb::WitnessData = serde_wasm_bindgen::from_value(witness_data)?; - let auth_data_proto: pb::AuthorizationData = serde_wasm_bindgen::from_value(auth_data)?; - + 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 plan: TransactionPlan = plan_proto.try_into()?; - - let tx: Transaction = plan.build( - &fvk, - &witness_data_proto.try_into()?, - &auth_data_proto.try_into()?, - )?; + let tx: Transaction = plan.build(&fvk, &witness, &auth)?; - let value = serde_wasm_bindgen::to_value(&tx.to_proto())?; - - Ok(value) + Ok(tx.encode_to_vec()) } /// Build parallel tx @@ -197,31 +152,22 @@ pub fn build( #[wasm_bindgen] pub fn build_parallel( actions: JsValue, - transaction_plan: JsValue, - witness_data: JsValue, - auth_data: JsValue, -) -> WasmResult { + transaction_plan: &[u8], + witness_data: &[u8], + auth_data: &[u8], +) -> WasmResult> { utils::set_panic_hook(); - let plan: TransactionPlan = serde_wasm_bindgen::from_value(transaction_plan.clone())?; - - let witness_data_proto: pb::WitnessData = serde_wasm_bindgen::from_value(witness_data)?; - let witness_data: WitnessData = witness_data_proto.try_into()?; - - let auth_data_proto: pb::AuthorizationData = serde_wasm_bindgen::from_value(auth_data)?; - let auth_data: AuthorizationData = auth_data_proto.try_into()?; - + 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_data)?; - - let tx = plan.apply_auth_data(&auth_data, transaction)?; + let transaction = plan.clone().build_unauth_with_actions(actions, &witness)?; - let value = serde_wasm_bindgen::to_value(&tx.to_proto())?; + let tx = plan.apply_auth_data(&auth, transaction)?; - Ok(value) + Ok(tx.encode_to_vec()) } /// Get transaction view, transaction perspective @@ -233,14 +179,14 @@ pub fn build_parallel( #[wasm_bindgen] pub async fn transaction_info( full_viewing_key: &[u8], - tx: JsValue, + tx: &[u8], idb_constants: JsValue, ) -> WasmResult { utils::set_panic_hook(); - let transaction = serde_wasm_bindgen::from_value(tx)?; + let transaction = Transaction::decode(tx)?; let constants = serde_wasm_bindgen::from_value(idb_constants)?; - let fvk: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; + let fvk = FullViewingKey::decode(full_viewing_key)?; let response = transaction_info_inner(fvk, transaction, constants).await?; let result = serde_wasm_bindgen::to_value(&response)?; diff --git a/packages/wasm/crate/src/utils.rs b/packages/wasm/crate/src/utils.rs index 0698576cb0..b1d7929dc9 100644 --- a/packages/wasm/crate/src/utils.rs +++ b/packages/wasm/crate/src/utils.rs @@ -1,11 +1,3 @@ -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; - -use penumbra_proto::DomainType; - -use crate::error::WasmResult; -use crate::utils; - pub fn set_panic_hook() { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then @@ -16,17 +8,3 @@ pub fn set_panic_hook() { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); } - -/// decode SCT root -/// Arguments: -/// tx_bytes: `HEX string` -/// Returns: `penumbra_tct::Root` -#[wasm_bindgen] -pub fn decode_sct_root(tx_bytes: &str) -> WasmResult { - utils::set_panic_hook(); - - let tx_vec: Vec = hex::decode(tx_bytes)?; - let root = penumbra_tct::Root::decode(tx_vec.as_slice())?; - let result = serde_wasm_bindgen::to_value(&root)?; - Ok(result) -} diff --git a/packages/wasm/crate/src/view_server.rs b/packages/wasm/crate/src/view_server.rs index 6fea5ddb8c..35ff214c8a 100644 --- a/packages/wasm/crate/src/view_server.rs +++ b/packages/wasm/crate/src/view_server.rs @@ -10,7 +10,6 @@ use penumbra_shielded_pool::note; use penumbra_tct as tct; use penumbra_tct::Witness::*; use serde::{Deserialize, Serialize}; -use serde_wasm_bindgen::Error; use serde_wasm_bindgen::Serializer; use tct::storage::{StoreCommitment, StoreHash, StoredPosition, Updates}; use tct::{Forgotten, Tree}; @@ -292,15 +291,14 @@ impl ViewServer { Ok(result) } - /// get SCT root /// SCT root can be compared with the root obtained by GRPC and verify that there is no divergence - /// Returns: `Root` + /// Returns: `Uint8Array representing a Root` #[wasm_bindgen] - pub fn get_sct_root(&mut self) -> Result { + pub fn get_sct_root(&mut self) -> WasmResult> { utils::set_panic_hook(); let root = self.sct.root(); - serde_wasm_bindgen::to_value(&root) + Ok(root.encode_to_vec()) } } diff --git a/packages/wasm/crate/tests/build.rs b/packages/wasm/crate/tests/build.rs index a217707c5d..2059abf964 100644 --- a/packages/wasm/crate/tests/build.rs +++ b/packages/wasm/crate/tests/build.rs @@ -2,7 +2,8 @@ extern crate penumbra_wasm; #[cfg(test)] mod tests { - use anyhow::Result; + use std::str::FromStr; + use indexed_db_futures::prelude::{ IdbDatabase, IdbObjectStore, IdbQuerySource, IdbTransaction, IdbTransactionMode, }; @@ -11,14 +12,13 @@ mod tests { 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, TransactionPlan as tp}, + asset::v1::Value, component::shielded_pool::v1::FmdParameters, keys::v1::Address, + transaction::v1::MemoPlaintext, }, view::v1::SpendableNoteRecord, DomainType, @@ -29,15 +29,14 @@ mod tests { plan::{ActionPlan, TransactionPlan}, Action, Transaction, }; + use prost::Message; use serde::{Deserialize, Serialize}; - use std::str::FromStr; use wasm_bindgen::JsValue; use wasm_bindgen_test::*; use penumbra_wasm::planner::plan_transaction; use penumbra_wasm::{ build::build_action, - error::WasmError, keys::load_proving_key, storage::IndexedDBStorage, tx::{authorize, build, build_parallel, witness}, @@ -59,23 +58,14 @@ mod tests { include_bytes!("../../../../apps/extension/bin/swapclaim_pk.bin"); let convert_key: &[u8] = include_bytes!("../../../../apps/extension/bin/convert_pk.bin"); - // Serialize &[u8] to JsValue. - let spend_key_js: JsValue = serde_wasm_bindgen::to_value(&spend_key).unwrap(); - let output_key_js: JsValue = serde_wasm_bindgen::to_value(&output_key).unwrap(); - let delegator_vote_key_js: JsValue = - serde_wasm_bindgen::to_value(&delegator_vote_key).unwrap(); - let swap_key_js: JsValue = serde_wasm_bindgen::to_value(&swap_key).unwrap(); - let swapclaim_key_js: JsValue = serde_wasm_bindgen::to_value(&swapclaim_key).unwrap(); - let convert_key_js: JsValue = serde_wasm_bindgen::to_value(&convert_key).unwrap(); - // Dynamically load the proving keys at runtime for each key type. - load_proving_key(spend_key_js, "spend").expect("can load spend key"); - load_proving_key(output_key_js, "output").expect("can load output key"); - load_proving_key(delegator_vote_key_js, "delegator_vote") + 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, "delegator_vote") .expect("can load delegator vote key"); - load_proving_key(swap_key_js, "swap").expect("can load swap key"); - load_proving_key(swapclaim_key_js, "swapclaim").expect("can load swapclaim key"); - load_proving_key(convert_key_js, "convert").expect("can load convert 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, "convert").expect("can load convert key"); // Define database parameters. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -428,13 +418,17 @@ mod tests { // Viewing key to reveal asset balances and transactions. let full_viewing_key = FullViewingKey::from_str("penumbrafullviewingkey1mnm04x7yx5tyznswlp0sxs8nsxtgxr9p98dp0msuek8fzxuknuzawjpct8zdevcvm3tsph0wvsuw33x2q42e7sf29q904hwerma8xzgrxsgq2").unwrap(); - let transaction_plan: JsValue = plan_transaction( + let plan_js_value: JsValue = plan_transaction( js_constants_params_value, - serde_wasm_bindgen::to_value(&planner_request).unwrap(), + &planner_request.encode_to_vec(), full_viewing_key.encode_to_vec().as_slice(), ) .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 -------------- @@ -443,11 +437,8 @@ mod tests { ) .unwrap(); - let authorization_data = authorize( - spend_key.encode_to_vec().as_slice(), - transaction_plan.clone(), - ) - .unwrap(); + let authorization_data = + authorize(spend_key.encode_to_vec().as_slice(), plan_slice).unwrap(); // -------------- 3. Generate witness -------------- @@ -504,44 +495,34 @@ mod tests { let sct_json = serde_wasm_bindgen::to_value(&sct).unwrap(); // Generate witness data from SCT and specific transaction plan. - let witness_data: Result = witness(transaction_plan.clone(), sct_json); - - // Serialize transaction plan into `TransactionPlan`. - let transaction_plan_serialized: tp = - serde_wasm_bindgen::from_value(transaction_plan.clone()).unwrap(); - let transaction_plan_conv: TransactionPlan = - transaction_plan_serialized.try_into().unwrap(); + 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 transaction_plan_conv.actions.clone() { + for i in plan.actions.clone() { if let ActionPlan::Spend(ref _spend_plan) = i { - let action_deserialize = serde_wasm_bindgen::to_value(&i).unwrap(); - let action = build_action( - transaction_plan.clone(), - action_deserialize, + let action_vec = build_action( + plan_slice, + i.encode_to_vec().as_slice(), full_viewing_key.encode_to_vec().as_slice(), - witness_data.as_ref().unwrap().clone(), + &witness_data, ) .unwrap(); - let action_serialize: Action = - serde_wasm_bindgen::from_value(action.clone()).unwrap(); - actions.push(action_serialize); + let action = Action::decode(&*action_vec).unwrap(); + actions.push(action); } if let ActionPlan::Output(ref _output_plan) = i { - let action_deserialize = serde_wasm_bindgen::to_value(&i).unwrap(); - let action = build_action( - transaction_plan.clone(), - action_deserialize, + let action_vec = build_action( + plan_slice, + i.encode_to_vec().as_slice(), full_viewing_key.encode_to_vec().as_slice(), - witness_data.as_ref().unwrap().clone(), + &witness_data, ) .unwrap(); - let action_serialize: Action = - serde_wasm_bindgen::from_value(action.clone()).unwrap(); - actions.push(action_serialize); + let action = Action::decode(&*action_vec).unwrap(); + actions.push(action); } } @@ -551,9 +532,9 @@ mod tests { // Execute parallel spend transaction and generate proof. let parallel_transaction = build_parallel( action_deserialized, - transaction_plan.clone(), - witness_data.as_ref().unwrap().clone(), - authorization_data.clone(), + plan_slice, + &witness_data, + &authorization_data, ) .unwrap(); console_log!("Parallel transaction is: {:?}", parallel_transaction); @@ -561,18 +542,16 @@ mod tests { // Execute serial spend transaction and generate proof. let serial_transaction = build( full_viewing_key.encode_to_vec().as_slice(), - transaction_plan.clone(), - witness_data.as_ref().unwrap().clone(), - authorization_data.clone(), + plan_slice, + &witness_data, + &authorization_data, ) .unwrap(); console_log!("Serial transaction is: {:?}", serial_transaction); // Deserialize transactions and stringify actions in the transaction body into JSON - let serial_result: Transaction = - serde_wasm_bindgen::from_value(serial_transaction).unwrap(); - let parallel_result: Transaction = - serde_wasm_bindgen::from_value(parallel_transaction).unwrap(); + 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(); diff --git a/packages/wasm/src/build.ts b/packages/wasm/src/build.ts index 6091b925e8..18a85e03c9 100644 --- a/packages/wasm/src/build.ts +++ b/packages/wasm/src/build.ts @@ -6,7 +6,6 @@ import { WitnessData, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; import type { StateCommitmentTree } from '@penumbra-zone/types/src/state-commitment-tree'; -import { JsonValue } from '@bufbuild/protobuf'; import { authorize, build_action, build_parallel, load_proving_key, witness } from '../wasm'; import { ActionType, provingKeys } from './proving-keys'; import { @@ -15,13 +14,13 @@ import { } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; export const authorizePlan = (spendKey: SpendKey, txPlan: TransactionPlan): AuthorizationData => { - const result = authorize(spendKey.toBinary(), txPlan.toJson()) as unknown; - return AuthorizationData.fromJsonString(JSON.stringify(result)); + const result = authorize(spendKey.toBinary(), txPlan.toBinary()); + return AuthorizationData.fromBinary(result); }; export const getWitness = (txPlan: TransactionPlan, sct: StateCommitmentTree): WitnessData => { - const result: unknown = witness(txPlan.toJson(), sct); - return WitnessData.fromJsonString(JSON.stringify(result)); + const result = witness(txPlan.toBinary(), sct); + return WitnessData.fromBinary(result); }; export const buildParallel = ( @@ -30,13 +29,13 @@ export const buildParallel = ( witnessData: WitnessData, authData: AuthorizationData, ): Transaction => { - const result: unknown = build_parallel( + const result = build_parallel( batchActions.map(action => action.toJson()), - txPlan.toJson(), - witnessData.toJson(), - authData.toJson(), + txPlan.toBinary(), + witnessData.toBinary(), + authData.toBinary(), ); - return Transaction.fromJson(result as JsonValue); + return Transaction.fromBinary(result); }; export const buildActionParallel = async ( @@ -46,23 +45,26 @@ export const buildActionParallel = async ( actionId: number, ): Promise => { // Conditionally read proving keys from disk and load keys into WASM binary - const actionType = txPlan.actions[actionId]?.action.case; - if (!actionType) throw new Error('No action key provided'); - await loadProvingKey(actionType); + const actionPlan = txPlan.actions[actionId]; + if (!actionPlan?.action.case) throw new Error('No action key provided'); + await loadProvingKey(actionPlan.action.case); const result = build_action( - txPlan.toJson(), - txPlan.actions[actionId]?.toJson(), + txPlan.toBinary(), + actionPlan.toBinary(), fullViewingKey.toBinary(), - witnessData.toJson(), - ) as unknown; + witnessData.toBinary(), + ); - return Action.fromJson(result as JsonValue); + return Action.fromBinary(result); }; const loadProvingKey = async (actionType: ActionType) => { const keyType = provingKeys[actionType]; if (!keyType) return; - const keyBin = (await fetch(`bin/${keyType}_pk.bin`)).arrayBuffer(); - load_proving_key(await keyBin, keyType); + + const res = await fetch(`bin/${keyType}_pk.bin`); + const buffer = await res.arrayBuffer(); + const uint8Array = new Uint8Array(buffer); + load_proving_key(uint8Array, keyType); }; diff --git a/packages/wasm/src/dex.ts b/packages/wasm/src/dex.ts index 2430295ae9..2886fb8bf7 100644 --- a/packages/wasm/src/dex.ts +++ b/packages/wasm/src/dex.ts @@ -5,17 +5,16 @@ import { PositionState, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/dex/v1/dex_pb'; import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { JsonValue } from '@bufbuild/protobuf'; export const computePositionId = (position: Position): PositionId => { - const result = compute_position_id(position.toJson()) as unknown; - return PositionId.fromJsonString(JSON.stringify(result)); + const bytes = compute_position_id(position.toBinary()); + return PositionId.fromBinary(bytes); }; export const getLpNftMetadata = ( positionId: PositionId, positionState: PositionState, ): Metadata => { - const result = get_lpnft_asset(positionId.toJson(), positionState.toJson()) as JsonValue; - return Metadata.fromJson(result); + const result = get_lpnft_asset(positionId.toBinary(), positionState.toBinary()); + return Metadata.fromBinary(result); }; diff --git a/packages/wasm/src/keys.ts b/packages/wasm/src/keys.ts index b2b5ca6ede..d4492294c0 100644 --- a/packages/wasm/src/keys.ts +++ b/packages/wasm/src/keys.ts @@ -18,11 +18,15 @@ export const generateSpendKey = (seedPhrase: string) => export const getFullViewingKey = (spendKey: SpendKey) => FullViewingKey.fromBinary(get_full_viewing_key(spendKey.toBinary())); -export const getAddressByIndex = (fullViewingKey: FullViewingKey, index: number) => - Address.fromBinary(get_address_by_index(fullViewingKey.toBinary(), index)); +export const getAddressByIndex = (fullViewingKey: FullViewingKey, index: number) => { + const bytes = get_address_by_index(fullViewingKey.toBinary(), index); + return Address.fromBinary(bytes); +}; -export const getEphemeralByIndex = (fullViewingKey: FullViewingKey, index: number) => - Address.fromBinary(get_ephemeral_address(fullViewingKey.toBinary(), index)); +export const getEphemeralByIndex = (fullViewingKey: FullViewingKey, index: number) => { + const bytes = get_ephemeral_address(fullViewingKey.toBinary(), index); + return Address.fromBinary(bytes); +}; export const getWalletId = (fullViewingKey: FullViewingKey) => WalletId.fromBinary(get_wallet_id(fullViewingKey.toBinary())); diff --git a/packages/wasm/src/planner.ts b/packages/wasm/src/planner.ts index 7f7c5f96af..c384998170 100644 --- a/packages/wasm/src/planner.ts +++ b/packages/wasm/src/planner.ts @@ -12,7 +12,7 @@ export const planTransaction = async ( ) => { const plan = (await plan_transaction( idbConstants, - request.toJson(), + request.toBinary(), fullViewingKey.toBinary(), )) as JsonValue; return TransactionPlan.fromJson(plan); diff --git a/packages/wasm/src/sct.test.ts b/packages/wasm/src/sct.test.ts deleted file mode 100644 index d27f4ecc88..0000000000 --- a/packages/wasm/src/sct.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { decodeSctRoot } from './sct'; - -describe('tct', () => { - describe('decodeSctRoot()', () => { - it('does not raise zod validation error', () => { - const root = new Uint8Array([ - 10, 32, 50, 68, 201, 21, 142, 211, 35, 124, 216, 158, 24, 100, 137, 54, 212, 195, 130, 17, - 160, 58, 0, 158, 208, 124, 120, 162, 25, 141, 214, 66, 221, 3, - ]); - - expect(() => { - decodeSctRoot(root); - }).not.toThrow(); - }); - }); -}); diff --git a/packages/wasm/src/sct.ts b/packages/wasm/src/sct.ts deleted file mode 100644 index 2371038fed..0000000000 --- a/packages/wasm/src/sct.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { decode_sct_root } from '../wasm'; -import { MerkleRoot } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb'; -import { JsonValue } from '@bufbuild/protobuf'; -import { uint8ArrayToHex } from '@penumbra-zone/types/src/hex'; - -export const decodeSctRoot = (hash: Uint8Array): MerkleRoot => { - const hexString = uint8ArrayToHex(hash); - const result = decode_sct_root(hexString) as JsonValue; - return MerkleRoot.fromJson(result); -}; diff --git a/packages/wasm/src/transaction.ts b/packages/wasm/src/transaction.ts index f0cbc80e93..f3530bff4a 100644 --- a/packages/wasm/src/transaction.ts +++ b/packages/wasm/src/transaction.ts @@ -5,8 +5,14 @@ import { TransactionView, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; import type { IdbConstants } from '@penumbra-zone/types/src/indexed-db'; +import { JsonValue } from '@bufbuild/protobuf'; import { FullViewingKey } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; +interface TxInfoWasmResult { + txp: JsonValue; + txv: JsonValue; +} + export const generateTransactionInfo = async ( fullViewingKey: FullViewingKey, tx: Transaction, @@ -14,14 +20,11 @@ export const generateTransactionInfo = async ( ) => { const { txp, txv } = (await transaction_info( fullViewingKey.toBinary(), - tx.toJson(), + tx.toBinary(), idbConstants, - )) as { - txp: unknown; - txv: unknown; - }; + )) as TxInfoWasmResult; return { - txp: TransactionPerspective.fromJsonString(JSON.stringify(txp)), - txv: TransactionView.fromJsonString(JSON.stringify(txv)), + txp: TransactionPerspective.fromJson(txp), + txv: TransactionView.fromJson(txv), }; }; diff --git a/packages/wasm/src/view-server.ts b/packages/wasm/src/view-server.ts index 821c490dc6..e1a3fdcf72 100644 --- a/packages/wasm/src/view-server.ts +++ b/packages/wasm/src/view-server.ts @@ -23,6 +23,13 @@ interface ViewServerProps { idbConstants: IdbConstants; } +interface FlushResult { + height?: string | number | bigint; + sct_updates?: JsonObject; + new_notes?: JsonValue[]; + new_swaps?: JsonValue[]; +} + export class ViewServer implements ViewServerInterface { private constructor( private wasmViewServer: WasmViewServer, @@ -66,30 +73,20 @@ export class ViewServer implements ViewServerInterface { } getSctRoot(): MerkleRoot { - const raw = this.wasmViewServer.get_sct_root() as JsonValue; - const res = MerkleRoot.fromJson(raw); - return res; + const bytes = this.wasmViewServer.get_sct_root(); + return MerkleRoot.fromBinary(bytes); } // As blocks are scanned, the internal wasmViewServer tree is being updated. // Flush updates clears the state and returns all the updates since the last checkpoint. flushUpdates(): ScanBlockResult { - const raw = this.wasmViewServer.flush_updates() as JsonValue; - const { height, sct_updates, new_notes, new_swaps } = raw as { - height?: string | number | bigint | undefined; - sct_updates?: JsonObject | undefined; - new_notes?: JsonValue[] | undefined; - new_swaps?: JsonValue[] | undefined; - }; - const wasmJson = { - new_notes: (new_notes ?? []).map(n => JSON.stringify(n)), - new_swaps: (new_swaps ?? []).map(s => JSON.stringify(s)), - }; + const result = this.wasmViewServer.flush_updates() as FlushResult; + const { height, sct_updates, new_notes, new_swaps } = result; return { - height: BigInt(height ?? false), + height: BigInt(height ?? 0), sctUpdates: validateSchema(SctUpdatesSchema, sct_updates), - newNotes: wasmJson.new_notes.map(n => SpendableNoteRecord.fromJsonString(n)), - newSwaps: wasmJson.new_swaps.map(s => SwapRecord.fromJsonString(s)), + newNotes: (new_notes ?? []).map(n => SpendableNoteRecord.fromJson(n)), + newSwaps: (new_swaps ?? []).map(s => SwapRecord.fromJson(s)), }; } }