From d6d8c134070c7afcab9e0da54dc7872ab1bfee37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 7 Oct 2022 16:04:16 +0200 Subject: [PATCH 1/3] move the current RPC patterns under "shell" sub-router --- apps/src/lib/client/rpc.rs | 15 +- shared/src/ledger/queries/mod.rs | 309 +------------------------- shared/src/ledger/queries/router.rs | 4 +- shared/src/ledger/queries/shell.rs | 332 ++++++++++++++++++++++++++++ 4 files changed, 348 insertions(+), 312 deletions(-) create mode 100644 shared/src/ledger/queries/shell.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 488f20f8f9..26f60313f0 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -46,7 +46,7 @@ use crate::facade::tendermint_rpc::{ /// Query the epoch of the last committed block pub async fn query_epoch(args: args::Query) -> Epoch { let client = HttpClient::new(args.ledger_address).unwrap(); - let epoch = unwrap_client_response(RPC.epoch(&client).await); + let epoch = unwrap_client_response(RPC.shell().epoch(&client).await); println!("Last committed epoch: {}", epoch); epoch } @@ -55,7 +55,7 @@ pub async fn query_epoch(args: args::Query) -> Epoch { pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); let bytes = unwrap_client_response( - RPC.storage_value(&client, &args.storage_key).await, + RPC.shell().storage_value(&client, &args.storage_key).await, ); match bytes { Some(bytes) => println!("Found data: 0x{}", HEXLOWER.encode(&bytes)), @@ -1032,7 +1032,8 @@ pub async fn dry_run_tx(ledger_address: &TendermintAddress, tx_bytes: Vec) { let client = HttpClient::new(ledger_address.clone()).unwrap(); let (data, height, prove) = (Some(tx_bytes), None, false); let result = unwrap_client_response( - RPC.dry_run_tx_with_options(&client, data, height, prove) + RPC.shell() + .dry_run_tx_with_options(&client, data, height, prove) .await, ) .data; @@ -1248,7 +1249,8 @@ pub async fn query_storage_value( where T: BorshDeserialize, { - let bytes = unwrap_client_response(RPC.storage_value(client, key).await); + let bytes = + unwrap_client_response(RPC.shell().storage_value(client, key).await); bytes.map(|bytes| { T::try_from_slice(&bytes[..]).unwrap_or_else(|err| { eprintln!("Error decoding the value: {}", err); @@ -1267,7 +1269,8 @@ pub async fn query_storage_prefix( where T: BorshDeserialize, { - let values = unwrap_client_response(RPC.storage_prefix(client, key).await); + let values = + unwrap_client_response(RPC.shell().storage_prefix(client, key).await); let decode = |PrefixValue { key, value }: PrefixValue| match T::try_from_slice( &value[..], @@ -1293,7 +1296,7 @@ pub async fn query_has_storage_key( client: &HttpClient, key: &storage::Key, ) -> bool { - unwrap_client_response(RPC.storage_has_key(client, key).await) + unwrap_client_response(RPC.shell().storage_has_key(client, key).await) } /// Represents a query for an event pertaining to the specified transaction diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 0c66faca1e..0bf21490eb 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -1,7 +1,7 @@ //! Ledger read-only queries can be handled and dispatched via the [`RPC`] //! defined via `router!` macro. -use tendermint_proto::crypto::{ProofOp, ProofOps}; +use shell::{Shell, SHELL}; #[cfg(any(test, feature = "async-client"))] pub use types::Client; pub use types::{ @@ -9,35 +9,17 @@ pub use types::{ }; use super::storage::{DBIter, StorageHasher, DB}; -use super::storage_api::{self, ResultExt, StorageRead}; -use crate::types::storage::{self, Epoch, PrefixValue}; -use crate::types::transaction::TxResult; -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] -use crate::types::transaction::{DecryptedTx, TxType}; +use super::storage_api; #[macro_use] mod router; +mod shell; mod types; // Most commonly expected patterns should be declared first router! {RPC, - // Epoch of the last committed block - ( "epoch" ) -> Epoch = epoch, - - // Raw storage access - read value - ( "value" / [storage_key: storage::Key] ) - -> Option> = storage_value, - - // Dry run a transaction - ( "dry_run_tx" ) -> TxResult = dry_run_tx, - - // Raw storage access - prefix iterator - ( "prefix" / [storage_key: storage::Key] ) - -> Vec = storage_prefix, - - // Raw storage access - is given storage key present? - ( "has_key" / [storage_key: storage::Key] ) - -> bool = storage_has_key, + // Shell provides storage read access, block metadata and can dry-run a tx + ( "shell" ) = (sub SHELL), } /// Handle RPC query request in the ledger. On success, returns response with @@ -86,188 +68,6 @@ pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { Ok(()) } -// Handlers: - -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] -fn dry_run_tx( - mut ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - use super::gas::BlockGasMeter; - use super::storage::write_log::WriteLog; - use crate::proto::Tx; - - let mut gas_meter = BlockGasMeter::default(); - let mut write_log = WriteLog::default(); - let tx = Tx::try_from(&request.data[..]).into_storage_result()?; - let tx = TxType::Decrypted(DecryptedTx::Decrypted(tx)); - let data = super::protocol::apply_tx( - tx, - request.data.len(), - &mut gas_meter, - &mut write_log, - ctx.storage, - &mut ctx.vp_wasm_cache, - &mut ctx.tx_wasm_cache, - ) - .into_storage_result()?; - Ok(ResponseQuery { - data, - ..ResponseQuery::default() - }) -} - -#[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] -fn dry_run_tx( - _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, -) -> storage_api::Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - unimplemented!( - "dry_run_tx request handler requires \"wasm-runtime\" and \ - \"ferveo-tpke\" features enabled." - ) -} - -fn epoch( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - require_latest_height(&ctx, request)?; - require_no_proof(request)?; - - let data = ctx.storage.last_epoch; - Ok(ResponseQuery { - data, - ..Default::default() - }) -} - -fn storage_value( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, - storage_key: storage::Key, -) -> storage_api::Result>>> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - match ctx - .storage - .read_with_height(&storage_key, request.height) - .into_storage_result()? - { - (Some(value), _gas) => { - let proof = if request.prove { - let proof = ctx - .storage - .get_existence_proof( - &storage_key, - value.clone().into(), - request.height, - ) - .into_storage_result()?; - Some(proof.into()) - } else { - None - }; - Ok(ResponseQuery { - data: Some(value), - proof_ops: proof, - ..Default::default() - }) - } - (None, _gas) => { - let proof = if request.prove { - let proof = ctx - .storage - .get_non_existence_proof(&storage_key, request.height) - .into_storage_result()?; - Some(proof.into()) - } else { - None - }; - Ok(ResponseQuery { - data: None, - proof_ops: proof, - info: format!("No value found for key: {}", storage_key), - }) - } - } -} - -fn storage_prefix( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, - storage_key: storage::Key, -) -> storage_api::Result>> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - require_latest_height(&ctx, request)?; - - let (iter, _gas) = ctx.storage.iter_prefix(&storage_key); - let data: storage_api::Result> = iter - .map(|(key, value, _gas)| { - let key = storage::Key::parse(key).into_storage_result()?; - Ok(PrefixValue { key, value }) - }) - .collect(); - let data = data?; - let proof_ops = if request.prove { - let mut ops = vec![]; - for PrefixValue { key, value } in &data { - let proof = ctx - .storage - .get_existence_proof(key, value.clone().into(), request.height) - .into_storage_result()?; - let mut cur_ops: Vec = - proof.ops.into_iter().map(|op| op.into()).collect(); - ops.append(&mut cur_ops); - } - // ops is not empty in this case - Some(ProofOps { ops }) - } else { - None - }; - Ok(ResponseQuery { - data, - proof_ops, - ..Default::default() - }) -} - -fn storage_has_key( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, - storage_key: storage::Key, -) -> storage_api::Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - require_latest_height(&ctx, request)?; - require_no_proof(request)?; - - let data = StorageRead::has_key(ctx.storage, &storage_key)?; - Ok(ResponseQuery { - data, - ..Default::default() - }) -} - #[cfg(any(test, feature = "tendermint-rpc"))] /// Provides [`Client`] implementation for Tendermint RPC client pub mod tm { @@ -418,102 +218,3 @@ mod testing { } } } - -#[cfg(test)] -mod test { - use borsh::BorshDeserialize; - - use super::testing::TestClient; - use super::*; - use crate::ledger::storage_api::StorageWrite; - use crate::proto::Tx; - use crate::types::{address, token}; - - const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; - - #[test] - fn test_queries_router_paths() { - let path = RPC.epoch_path(); - assert_eq!("/epoch", path); - - let token_addr = address::testing::established_address_1(); - let owner = address::testing::established_address_2(); - let key = token::balance_key(&token_addr, &owner); - let path = RPC.storage_value_path(&key); - assert_eq!(format!("/value/{}", key), path); - - let path = RPC.dry_run_tx_path(); - assert_eq!("/dry_run_tx", path); - - let path = RPC.storage_prefix_path(&key); - assert_eq!(format!("/prefix/{}", key), path); - - let path = RPC.storage_has_key_path(&key); - assert_eq!(format!("/has_key/{}", key), path); - } - - #[tokio::test] - async fn test_queries_router_with_client() -> storage_api::Result<()> { - // Initialize the `TestClient` - let mut client = TestClient::new(RPC); - - // Request last committed epoch - let read_epoch = RPC.epoch(&client).await.unwrap(); - let current_epoch = client.storage.last_epoch; - assert_eq!(current_epoch, read_epoch); - - // Request dry run tx - let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); - let tx = Tx::new(tx_no_op, None); - let tx_bytes = tx.to_bytes(); - let result = RPC - .dry_run_tx_with_options(&client, Some(tx_bytes), None, false) - .await - .unwrap(); - assert!(result.data.is_accepted()); - - // Request storage value for a balance key ... - let token_addr = address::testing::established_address_1(); - let owner = address::testing::established_address_2(); - let balance_key = token::balance_key(&token_addr, &owner); - // ... there should be no value yet. - let read_balance = - RPC.storage_value(&client, &balance_key).await.unwrap(); - assert!(read_balance.is_none()); - - // Request storage prefix iterator - let balance_prefix = token::balance_prefix(&token_addr); - let read_balances = - RPC.storage_prefix(&client, &balance_prefix).await.unwrap(); - assert!(read_balances.is_empty()); - - // Request storage has key - let has_balance_key = - RPC.storage_has_key(&client, &balance_key).await.unwrap(); - assert!(!has_balance_key); - - // Then write some balance ... - let balance = token::Amount::from(1000); - StorageWrite::write(&mut client.storage, &balance_key, balance)?; - // ... there should be the same value now - let read_balance = - RPC.storage_value(&client, &balance_key).await.unwrap(); - assert_eq!( - balance, - token::Amount::try_from_slice(&read_balance.unwrap()).unwrap() - ); - - // Request storage prefix iterator - let balance_prefix = token::balance_prefix(&token_addr); - let read_balances = - RPC.storage_prefix(&client, &balance_prefix).await.unwrap(); - assert_eq!(read_balances.len(), 1); - - // Request storage has key - let has_balance_key = - RPC.storage_has_key(&client, &balance_key).await.unwrap(); - assert!(has_balance_key); - - Ok(()) - } -} diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index 69888cb935..df36167eb8 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -494,7 +494,7 @@ macro_rules! router_type { impl $name { #[doc = "Construct this router as a root router"] - const fn new() -> Self { + pub const fn new() -> Self { Self { prefix: String::new(), } @@ -502,7 +502,7 @@ macro_rules! router_type { #[allow(dead_code)] #[doc = "Construct this router as a sub-router at the given prefix path"] - const fn sub(prefix: String) -> Self { + pub const fn sub(prefix: String) -> Self { Self { prefix, } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs new file mode 100644 index 0000000000..2304a0421e --- /dev/null +++ b/shared/src/ledger/queries/shell.rs @@ -0,0 +1,332 @@ +use tendermint_proto::crypto::{ProofOp, ProofOps}; + +use crate::ledger::queries::types::{RequestCtx, RequestQuery, ResponseQuery}; +use crate::ledger::queries::{require_latest_height, require_no_proof}; +use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::types::storage::{self, Epoch, PrefixValue}; +use crate::types::transaction::TxResult; +#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +use crate::types::transaction::{DecryptedTx, TxType}; + +router! {SHELL, + // Epoch of the last committed block + ( "epoch" ) -> Epoch = epoch, + + // Raw storage access - read value + ( "value" / [storage_key: storage::Key] ) + -> Option> = storage_value, + + // Dry run a transaction + ( "dry_run_tx" ) -> TxResult = dry_run_tx, + + // Raw storage access - prefix iterator + ( "prefix" / [storage_key: storage::Key] ) + -> Vec = storage_prefix, + + // Raw storage access - is given storage key present? + ( "has_key" / [storage_key: storage::Key] ) + -> bool = storage_has_key, +} + +// Handlers: + +#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +fn dry_run_tx( + mut ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + use crate::ledger::gas::BlockGasMeter; + use crate::ledger::protocol; + use crate::ledger::storage::write_log::WriteLog; + use crate::proto::Tx; + + let mut gas_meter = BlockGasMeter::default(); + let mut write_log = WriteLog::default(); + let tx = Tx::try_from(&request.data[..]).into_storage_result()?; + let tx = TxType::Decrypted(DecryptedTx::Decrypted(tx)); + let data = protocol::apply_tx( + tx, + request.data.len(), + &mut gas_meter, + &mut write_log, + ctx.storage, + &mut ctx.vp_wasm_cache, + &mut ctx.tx_wasm_cache, + ) + .into_storage_result()?; + Ok(ResponseQuery { + data, + ..ResponseQuery::default() + }) +} + +#[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] +fn dry_run_tx( + _ctx: RequestCtx<'_, D, H>, + _request: &RequestQuery, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + unimplemented!( + "dry_run_tx request handler requires \"wasm-runtime\" and \ + \"ferveo-tpke\" features enabled." + ) +} + +fn epoch( + ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + require_latest_height(&ctx, request)?; + require_no_proof(request)?; + + let data = ctx.storage.last_epoch; + Ok(ResponseQuery { + data, + ..Default::default() + }) +} + +fn storage_value( + ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, + storage_key: storage::Key, +) -> storage_api::Result>>> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + match ctx + .storage + .read_with_height(&storage_key, request.height) + .into_storage_result()? + { + (Some(data), _gas) => { + let proof = if request.prove { + let proof = ctx + .storage + .get_existence_proof( + &storage_key, + data.clone(), + request.height, + ) + .into_storage_result()?; + Some(proof.into()) + } else { + None + }; + Ok(ResponseQuery { + data: Some(data), + proof_ops: proof, + ..Default::default() + }) + } + (None, _gas) => { + let proof = if request.prove { + let proof = ctx + .storage + .get_non_existence_proof(&storage_key, request.height) + .into_storage_result()?; + Some(proof.into()) + } else { + None + }; + Ok(ResponseQuery { + data: None, + proof_ops: proof, + info: format!("No value found for key: {}", storage_key), + }) + } + } +} + +fn storage_prefix( + ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, + storage_key: storage::Key, +) -> storage_api::Result>> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + require_latest_height(&ctx, request)?; + + let (iter, _gas) = ctx.storage.iter_prefix(&storage_key); + let data: storage_api::Result> = iter + .map(|(key, value, _gas)| { + let key = storage::Key::parse(key).into_storage_result()?; + Ok(PrefixValue { key, value }) + }) + .collect(); + let data = data?; + let proof_ops = if request.prove { + let mut ops = vec![]; + for PrefixValue { key, value } in &data { + let proof = ctx + .storage + .get_existence_proof(key, value.clone(), request.height) + .into_storage_result()?; + let mut cur_ops: Vec = + proof.ops.into_iter().map(|op| op.into()).collect(); + ops.append(&mut cur_ops); + } + // ops is not empty in this case + Some(ProofOps { ops }) + } else { + None + }; + Ok(ResponseQuery { + data, + proof_ops, + ..Default::default() + }) +} + +fn storage_has_key( + ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, + storage_key: storage::Key, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + require_latest_height(&ctx, request)?; + require_no_proof(request)?; + + let data = StorageRead::has_key(ctx.storage, &storage_key)?; + Ok(ResponseQuery { + data, + ..Default::default() + }) +} + +#[cfg(test)] +mod test { + use borsh::BorshDeserialize; + + use crate::ledger::queries::testing::TestClient; + use crate::ledger::queries::RPC; + use crate::ledger::storage_api::{self, StorageWrite}; + use crate::proto::Tx; + use crate::types::{address, token}; + + const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; + + #[test] + fn test_shell_queries_router_paths() { + let path = RPC.shell().epoch_path(); + assert_eq!("/shell/epoch", path); + + let token_addr = address::testing::established_address_1(); + let owner = address::testing::established_address_2(); + let key = token::balance_key(&token_addr, &owner); + let path = RPC.shell().storage_value_path(&key); + assert_eq!(format!("/shell/value/{}", key), path); + + let path = RPC.shell().dry_run_tx_path(); + assert_eq!("/shell/dry_run_tx", path); + + let path = RPC.shell().storage_prefix_path(&key); + assert_eq!(format!("/shell/prefix/{}", key), path); + + let path = RPC.shell().storage_has_key_path(&key); + assert_eq!(format!("/shell/has_key/{}", key), path); + } + + #[tokio::test] + async fn test_shell_queries_router_with_client() -> storage_api::Result<()> + { + // Initialize the `TestClient` + let mut client = TestClient::new(RPC); + + // Request last committed epoch + let read_epoch = RPC.shell().epoch(&client).await.unwrap(); + let current_epoch = client.storage.last_epoch; + assert_eq!(current_epoch, read_epoch); + + // Request dry run tx + let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); + let tx = Tx::new(tx_no_op, None); + let tx_bytes = tx.to_bytes(); + let result = RPC + .shell() + .dry_run_tx_with_options(&client, Some(tx_bytes), None, false) + .await + .unwrap(); + assert!(result.data.is_accepted()); + + // Request storage value for a balance key ... + let token_addr = address::testing::established_address_1(); + let owner = address::testing::established_address_2(); + let balance_key = token::balance_key(&token_addr, &owner); + // ... there should be no value yet. + let read_balance = RPC + .shell() + .storage_value(&client, &balance_key) + .await + .unwrap(); + assert!(read_balance.is_none()); + + // Request storage prefix iterator + let balance_prefix = token::balance_prefix(&token_addr); + let read_balances = RPC + .shell() + .storage_prefix(&client, &balance_prefix) + .await + .unwrap(); + assert!(read_balances.is_empty()); + + // Request storage has key + let has_balance_key = RPC + .shell() + .storage_has_key(&client, &balance_key) + .await + .unwrap(); + assert!(!has_balance_key); + + // Then write some balance ... + let balance = token::Amount::from(1000); + StorageWrite::write(&mut client.storage, &balance_key, balance)?; + // ... there should be the same value now + let read_balance = RPC + .shell() + .storage_value(&client, &balance_key) + .await + .unwrap(); + assert_eq!( + balance, + token::Amount::try_from_slice(&read_balance.unwrap()).unwrap() + ); + + // Request storage prefix iterator + let balance_prefix = token::balance_prefix(&token_addr); + let read_balances = RPC + .shell() + .storage_prefix(&client, &balance_prefix) + .await + .unwrap(); + assert_eq!(read_balances.len(), 1); + + // Request storage has key + let has_balance_key = RPC + .shell() + .storage_has_key(&client, &balance_key) + .await + .unwrap(); + assert!(has_balance_key); + + Ok(()) + } +} From eb921d3098a853d1ef7c9a579bbdfd15e79212cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 14 Oct 2022 16:38:56 +0200 Subject: [PATCH 2/3] router: add `with_options` for handlers that use request/response --- apps/src/lib/client/rpc.rs | 32 ++-- shared/src/ledger/queries/mod.rs | 14 +- shared/src/ledger/queries/router.rs | 247 ++++++++++++++++++++++------ shared/src/ledger/queries/shell.rs | 58 +++---- 4 files changed, 248 insertions(+), 103 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 26f60313f0..f6af362aeb 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -54,10 +54,12 @@ pub async fn query_epoch(args: args::Query) -> Epoch { /// Query the raw bytes of given storage key pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); - let bytes = unwrap_client_response( - RPC.shell().storage_value(&client, &args.storage_key).await, + let response = unwrap_client_response( + RPC.shell() + .storage_value(&client, None, None, false, &args.storage_key) + .await, ); - match bytes { + match response.data { Some(bytes) => println!("Found data: 0x{}", HEXLOWER.encode(&bytes)), None => println!("No data found for key {}", args.storage_key), } @@ -1032,9 +1034,7 @@ pub async fn dry_run_tx(ledger_address: &TendermintAddress, tx_bytes: Vec) { let client = HttpClient::new(ledger_address.clone()).unwrap(); let (data, height, prove) = (Some(tx_bytes), None, false); let result = unwrap_client_response( - RPC.shell() - .dry_run_tx_with_options(&client, data, height, prove) - .await, + RPC.shell().dry_run_tx(&client, data, height, prove).await, ) .data; println!("Dry-run result: {}", result); @@ -1249,9 +1249,12 @@ pub async fn query_storage_value( where T: BorshDeserialize, { - let bytes = - unwrap_client_response(RPC.shell().storage_value(client, key).await); - bytes.map(|bytes| { + let response = unwrap_client_response( + RPC.shell() + .storage_value(client, None, None, false, key) + .await, + ); + response.data.map(|bytes| { T::try_from_slice(&bytes[..]).unwrap_or_else(|err| { eprintln!("Error decoding the value: {}", err); cli::safe_exit(1) @@ -1269,8 +1272,11 @@ pub async fn query_storage_prefix( where T: BorshDeserialize, { - let values = - unwrap_client_response(RPC.shell().storage_prefix(client, key).await); + let values = unwrap_client_response( + RPC.shell() + .storage_prefix(client, None, None, false, key) + .await, + ); let decode = |PrefixValue { key, value }: PrefixValue| match T::try_from_slice( &value[..], @@ -1284,10 +1290,10 @@ where } Ok(value) => Some((key, value)), }; - if values.is_empty() { + if values.data.is_empty() { None } else { - Some(values.into_iter().filter_map(decode)) + Some(values.data.into_iter().filter_map(decode)) } } diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 0bf21490eb..b2b7ef75ba 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -57,8 +57,7 @@ where Ok(()) } -/// For queries that only support latest height, check that the given height is -/// not different from latest height, otherwise return an error. +/// For queries that don't support proofs, require that they are not requested. pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { if request.prove { return Err(storage_api::Error::new_const( @@ -68,6 +67,17 @@ pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { Ok(()) } +/// For queries that don't use request data, require that there are no data +/// attached. +pub fn require_no_data(request: &RequestQuery) -> storage_api::Result<()> { + if !request.data.is_empty() { + return Err(storage_api::Error::new_const( + "This query doesn't accept request data", + )); + } + Ok(()) +} + #[cfg(any(test, feature = "tendermint-rpc"))] /// Provides [`Client`] implementation for Tendermint RPC client pub mod tm { diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index df36167eb8..b522425a35 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -47,10 +47,10 @@ macro_rules! handle_match { return $router.internal_handle($ctx, $request, $start) }; - // Handler function + // Handler function that uses a request (`with_options`) ( $ctx:ident, $request:ident, $start:ident, $end:ident, - $handle:tt, ( $( $matched_args:ident, )* ), + (with_options $handle:tt), ( $( $matched_args:ident, )* ), ) => { // check that we're at the end of the path - trailing slash is optional if !($end == $request.path.len() || @@ -60,8 +60,6 @@ macro_rules! handle_match { println!("Not fully matched"); break } - // If you get a compile error from here with `expected function, found - // queries::Storage`, you're probably missing the marker `(sub _)` let result = $handle($ctx, $request, $( $matched_args ),* )?; let data = borsh::BorshSerialize::try_to_vec(&result.data).into_storage_result()?; return Ok($crate::ledger::queries::EncodedResponseQuery { @@ -70,6 +68,35 @@ macro_rules! handle_match { proof_ops: result.proof_ops, }); }; + + // Handler function that doesn't use the request, just the path args, if any + ( + $ctx:ident, $request:ident, $start:ident, $end:ident, + $handle:tt, ( $( $matched_args:ident, )* ), + ) => { + // check that we're at the end of the path - trailing slash is optional + if !($end == $request.path.len() || + // ignore trailing slashes + $end == $request.path.len() - 1 && &$request.path[$end..] == "/") { + // we're not at the end, no match + println!("Not fully matched"); + break + } + // Check that the request is not sent with unsupported non-default + $crate::ledger::queries::require_latest_height(&$ctx, $request)?; + $crate::ledger::queries::require_no_proof($request)?; + $crate::ledger::queries::require_no_data($request)?; + + // If you get a compile error from here with `expected function, found + // queries::Storage`, you're probably missing the marker `(sub _)` + let data = $handle($ctx, $( $matched_args ),* )?; + let data = borsh::BorshSerialize::try_to_vec(&data).into_storage_result()?; + return Ok($crate::ledger::queries::EncodedResponseQuery { + data, + info: Default::default(), + proof_ops: None, + }); + }; } /// Using TT muncher pattern on the `$tail` pattern, this macro recursively @@ -167,16 +194,18 @@ macro_rules! try_match_segments { ( $( $matched_args, )* $arg, ), ( $( $( $tail )/ * )? ) ); }; - // Special case of the pattern below. When there are no more args in the - // tail and the handle isn't a sub-router (its fragment is ident), we try - // to match the rest of the path till the end. This is specifically needed - // for storage methods, which have `storage::Key` param that includes - // path-like slashes. + // Special case of the typed argument pattern below. When there are no more + // args in the tail and the handle isn't a sub-router (its handler is + // ident), we try to match the rest of the path till the end. + // + // This is specifically needed for storage methods, which have + // `storage::Key` param that includes path-like slashes. // // Try to match and parse a typed argument, declares the expected $arg into // type $t, if it can be parsed ( - $ctx:ident, $request:ident, $start:ident, $end:ident, $handle:ident, + $ctx:ident, $request:ident, $start:ident, $end:ident, + $handle:ident, ( $( $matched_args:ident, )* ), ( [$arg:ident : $arg_ty:ty] @@ -201,6 +230,41 @@ macro_rules! try_match_segments { ( $( $matched_args, )* $arg, ), () ); }; + // One more special case of the typed argument pattern below for a handler + // `with_options`, where we try to match the rest of the path till the end. + // + // This is specifically needed for storage methods, which have + // `storage::Key` param that includes path-like slashes. + // + // Try to match and parse a typed argument, declares the expected $arg into + // type $t, if it can be parsed + ( + $ctx:ident, $request:ident, $start:ident, $end:ident, + (with_options $handle:ident), + ( $( $matched_args:ident, )* ), + ( + [$arg:ident : $arg_ty:ty] + ) + ) => { + let $arg: $arg_ty; + $end = $request.path.len(); + match $request.path[$start..$end].parse::<$arg_ty>() { + Ok(parsed) => { + println!("Parsed {}", parsed); + $arg = parsed + }, + Err(_) => + { + println!("Cannot parse {} from {}", stringify!($arg_ty), &$request.path[$start..$end]); + // If arg cannot be parsed, try to skip to next pattern + break + } + } + // Invoke the terminal pattern + try_match_segments!($ctx, $request, $start, $end, (with_options $handle), + ( $( $matched_args, )* $arg, ), () ); + }; + // Try to match and parse a typed argument, declares the expected $arg into // type $t, if it can be parsed ( @@ -307,13 +371,12 @@ macro_rules! pattern_to_prefix { /// Turn patterns and their handlers into methods for the router, where each /// dynamic pattern is turned into a parameter for the method. macro_rules! pattern_and_handler_to_method { - // terminal rule + // terminal rule for $handle that uses request (`with_options`) ( ( $( $param:tt: $param_ty:ty ),* ) [ $( { $prefix:expr } ),* ] - // $( $return_type:path )?, $return_type:path, - $handle:tt, + (with_options $handle:tt), () ) => { // paste! used to construct the `fn $handle_path`'s name. @@ -327,29 +390,6 @@ macro_rules! pattern_and_handler_to_method { .filter_map(|x| x), "/") } - #[allow(dead_code)] - #[allow(clippy::too_many_arguments)] - #[cfg(any(test, feature = "async-client"))] - #[doc = "Request a simple borsh-encoded value from `" $handle "`, \ - without any additional request data, specified block height or \ - proof."] - pub async fn $handle(&self, client: &CLIENT, - $( $param: &$param_ty ),* - ) - -> std::result::Result< - $return_type, - ::Error - > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { - let path = self.[<$handle _path>]( $( $param ),* ); - - let data = client.simple_request(path).await?; - - let decoded: $return_type = - borsh::BorshDeserialize::try_from_slice(&data[..])?; - Ok(decoded) - } - #[allow(dead_code)] #[allow(clippy::too_many_arguments)] #[cfg(any(test, feature = "async-client"))] @@ -357,7 +397,7 @@ macro_rules! pattern_and_handler_to_method { `dry_run_tx`), optionally specified height (supported for \ `storage_value`) and optional proof (supported for \ `storage_value` and `storage_prefix`) from `" $handle "`."] - pub async fn [<$handle _with_options>](&self, client: &CLIENT, + pub async fn $handle(&self, client: &CLIENT, data: Option>, height: Option<$crate::types::storage::BlockHeight>, prove: bool, @@ -386,6 +426,50 @@ macro_rules! pattern_and_handler_to_method { } }; + // terminal rule that $handle that doesn't use request + ( + ( $( $param:tt: $param_ty:ty ),* ) + [ $( { $prefix:expr } ),* ] + $return_type:path, + $handle:tt, + () + ) => { + // paste! used to construct the `fn $handle_path`'s name. + paste::paste! { + #[allow(dead_code)] + #[doc = "Get a path to query `" $handle "`."] + pub fn [<$handle _path>](&self, $( $param: &$param_ty ),* ) -> String { + itertools::join( + [ Some(std::borrow::Cow::from(&self.prefix)), $( $prefix ),* ] + .into_iter() + .filter_map(|x| x), "/") + } + + #[allow(dead_code)] + #[allow(clippy::too_many_arguments)] + #[cfg(any(test, feature = "async-client"))] + #[doc = "Request a simple borsh-encoded value from `" $handle "`, \ + without any additional request data, specified block height or \ + proof."] + pub async fn $handle(&self, client: &CLIENT, + $( $param: &$param_ty ),* + ) + -> std::result::Result< + $return_type, + ::Error + > + where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + let path = self.[<$handle _path>]( $( $param ),* ); + + let data = client.simple_request(path).await?; + + let decoded: $return_type = + borsh::BorshDeserialize::try_from_slice(&data[..])?; + Ok(decoded) + } + } + }; + // sub-pattern ( $param:tt @@ -580,6 +664,61 @@ macro_rules! router_type { /// methods (enabled with `feature = "async-client"`). /// /// The `router!` macro implements greedy matching algorithm. +/// +/// ## Examples +/// +/// ```rust,ignore +/// router! {ROOT, +/// // This pattern matches `/pattern_a/something`, where `something` can be +/// // parsed with `FromStr` into `ArgType`. +/// ( "pattern_a" / [typed_dynamic_arg: ArgType] ) -> ReturnType = handler, +/// +/// ( "pattern_b" / [optional_dynamic_arg: opt ArgType] ) -> ReturnType = +/// handler, +/// +/// // Untyped dynamic arg is a string slice `&str` +/// ( "pattern_c" / [untyped_dynamic_arg] ) -> ReturnType = handler, +/// +/// // The handler additionally receives the `RequestQuery`, which can have +/// // some data attached, specified block height and ask for a proof. It +/// // returns `ResponseQuery`, which can have some `info` string and a proof. +/// ( "pattern_d" ) -> ReturnType = (with_options handler), +/// +/// ( "another" / "pattern" / "that" / "goes" / "deep" ) -> ReturnType = handler, +/// +/// // Inlined sub-tree +/// ( "subtree" / [this_is_fine: ArgType] ) = { +/// ( "a" ) -> u64 = a_handler, +/// ( "b" / [another_arg] ) -> u64 = b_handler, +/// } +/// +/// // Imported sub-router - The prefix can only have literal segments +/// ( "sub" / "no_dynamic_args" ) = (sub SUB_ROUTER), +/// } +/// +/// router! {SUB_ROUTER, +/// ( "pattern" ) -> ReturnType = handler, +/// } +/// ``` +/// +/// Handler functions used in the patterns should have the expected signature: +/// ```rust,ignore +/// fn handler(ctx: RequestCtx<'_, D, H>, args ...) +/// -> storage_api::Result +/// where +/// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, +/// H: 'static + StorageHasher + Sync; +/// ``` +/// +/// If the handler wants to support request options, it can be defined as +/// `(with_options $handler)` and then the expected signature is: +/// ```rust,ignore +/// fn handler(ctx: RequestCtx<'_, D, H>, request: &RequestQuery, args +/// ...) -> storage_api::Result> +/// where +/// D: 'static + DB + for<'iter> DBIter<'iter> + Sync, +/// H: 'static + StorageHasher + Sync; +/// ``` #[macro_export] macro_rules! router { { $name:ident, $( $pattern:tt $( -> $return_type:path )? = $handle:tt , )* } => ( @@ -658,9 +797,8 @@ mod test_rpc_handlers { $( pub fn $name( _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, $( $( $param: $param_ty ),* )? - ) -> storage_api::Result> + ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -669,10 +807,7 @@ mod test_rpc_handlers { $( $( let data = format!("{data}/{}", $param); )* )? - Ok(ResponseQuery { - data, - ..ResponseQuery::default() - }) + Ok(data) } )* }; @@ -697,11 +832,10 @@ mod test_rpc_handlers { /// support optional args. pub fn b3iii( _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, a1: token::Amount, a2: token::Amount, a3: Option, - ) -> storage_api::Result> + ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -710,22 +844,18 @@ mod test_rpc_handlers { let data = format!("{data}/{}", a1); let data = format!("{data}/{}", a2); let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); - Ok(ResponseQuery { - data, - ..ResponseQuery::default() - }) + Ok(data) } /// This handler is hand-written, because the test helper macro doesn't /// support optional args. pub fn b3iiii( _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, a1: token::Amount, a2: token::Amount, a3: Option, a4: Option, - ) -> storage_api::Result> + ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -735,6 +865,20 @@ mod test_rpc_handlers { let data = format!("{data}/{}", a2); let data = a3.map(|a3| format!("{data}/{}", a3)).unwrap_or(data); let data = a4.map(|a4| format!("{data}/{}", a4)).unwrap_or(data); + Ok(data) + } + + /// This handler is hand-written, because the test helper macro doesn't + /// support handlers with `with_options`. + pub fn c( + _ctx: RequestCtx<'_, D, H>, + _request: &RequestQuery, + ) -> storage_api::Result> + where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + { + let data = "c".to_owned(); Ok(ResponseQuery { data, ..ResponseQuery::default() @@ -773,6 +917,7 @@ mod test_rpc { ( "iiii" / [a3: opt token::Amount] / "xyz" / [a4: opt Epoch] ) -> String = b3iiii, }, }, + ( "c" ) -> String = (with_options c), } router! {TEST_SUB_RPC, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 2304a0421e..8ba800023c 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -1,7 +1,7 @@ use tendermint_proto::crypto::{ProofOp, ProofOps}; +use crate::ledger::queries::require_latest_height; use crate::ledger::queries::types::{RequestCtx, RequestQuery, ResponseQuery}; -use crate::ledger::queries::{require_latest_height, require_no_proof}; use crate::ledger::storage::{DBIter, StorageHasher, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; use crate::types::storage::{self, Epoch, PrefixValue}; @@ -15,14 +15,14 @@ router! {SHELL, // Raw storage access - read value ( "value" / [storage_key: storage::Key] ) - -> Option> = storage_value, + -> Option> = (with_options storage_value), // Dry run a transaction - ( "dry_run_tx" ) -> TxResult = dry_run_tx, + ( "dry_run_tx" ) -> TxResult = (with_options dry_run_tx), // Raw storage access - prefix iterator ( "prefix" / [storage_key: storage::Key] ) - -> Vec = storage_prefix, + -> Vec = (with_options storage_prefix), // Raw storage access - is given storage key present? ( "has_key" / [storage_key: storage::Key] ) @@ -80,22 +80,13 @@ where ) } -fn epoch( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result> +fn epoch(ctx: RequestCtx<'_, D, H>) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - require_latest_height(&ctx, request)?; - require_no_proof(request)?; - let data = ctx.storage.last_epoch; - Ok(ResponseQuery { - data, - ..Default::default() - }) + Ok(data) } fn storage_value( @@ -112,13 +103,13 @@ where .read_with_height(&storage_key, request.height) .into_storage_result()? { - (Some(data), _gas) => { + (Some(value), _gas) => { let proof = if request.prove { let proof = ctx .storage .get_existence_proof( &storage_key, - data.clone(), + value.clone().into(), request.height, ) .into_storage_result()?; @@ -127,7 +118,7 @@ where None }; Ok(ResponseQuery { - data: Some(data), + data: Some(value), proof_ops: proof, ..Default::default() }) @@ -175,7 +166,7 @@ where for PrefixValue { key, value } in &data { let proof = ctx .storage - .get_existence_proof(key, value.clone(), request.height) + .get_existence_proof(key, value.clone().into(), request.height) .into_storage_result()?; let mut cur_ops: Vec = proof.ops.into_iter().map(|op| op.into()).collect(); @@ -195,21 +186,14 @@ where fn storage_has_key( ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, storage_key: storage::Key, -) -> storage_api::Result> +) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - require_latest_height(&ctx, request)?; - require_no_proof(request)?; - let data = StorageRead::has_key(ctx.storage, &storage_key)?; - Ok(ResponseQuery { - data, - ..Default::default() - }) + Ok(data) } #[cfg(test)] @@ -262,7 +246,7 @@ mod test { let tx_bytes = tx.to_bytes(); let result = RPC .shell() - .dry_run_tx_with_options(&client, Some(tx_bytes), None, false) + .dry_run_tx(&client, Some(tx_bytes), None, false) .await .unwrap(); assert!(result.data.is_accepted()); @@ -274,19 +258,19 @@ mod test { // ... there should be no value yet. let read_balance = RPC .shell() - .storage_value(&client, &balance_key) + .storage_value(&client, None, None, false, &balance_key) .await .unwrap(); - assert!(read_balance.is_none()); + assert!(read_balance.data.is_none()); // Request storage prefix iterator let balance_prefix = token::balance_prefix(&token_addr); let read_balances = RPC .shell() - .storage_prefix(&client, &balance_prefix) + .storage_prefix(&client, None, None, false, &balance_prefix) .await .unwrap(); - assert!(read_balances.is_empty()); + assert!(read_balances.data.is_empty()); // Request storage has key let has_balance_key = RPC @@ -302,22 +286,22 @@ mod test { // ... there should be the same value now let read_balance = RPC .shell() - .storage_value(&client, &balance_key) + .storage_value(&client, None, None, false, &balance_key) .await .unwrap(); assert_eq!( balance, - token::Amount::try_from_slice(&read_balance.unwrap()).unwrap() + token::Amount::try_from_slice(&read_balance.data.unwrap()).unwrap() ); // Request storage prefix iterator let balance_prefix = token::balance_prefix(&token_addr); let read_balances = RPC .shell() - .storage_prefix(&client, &balance_prefix) + .storage_prefix(&client, None, None, false, &balance_prefix) .await .unwrap(); - assert_eq!(read_balances.len(), 1); + assert_eq!(read_balances.data.len(), 1); // Request storage has key let has_balance_key = RPC From e550e12fdb27f99cbffb6c4212a74e9581869d27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 24 Oct 2022 13:50:06 +0000 Subject: [PATCH 3/3] [ci] wasm checksums update --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index f3e11c7ea5..98d4134fd3 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.99da6abae7acd0a67341b8581bc2b9667cadd8d8c947086a6ed62cb8e5ab9f01.wasm", - "tx_ibc.wasm": "tx_ibc.449da8289b55d2e8ee4b80c4a271f0f82c14bc52f5f1cc141fc2fc25d7c379dc.wasm", - "tx_init_account.wasm": "tx_init_account.c7cf064e8d03315763d0a1c75b9fc0d44d98eab6a2943a82053a8d555861a14e.wasm", - "tx_init_nft.wasm": "tx_init_nft.7e7a5a2678da391ee89b02f08c75fab8b9f48a40e9d4d871d159694080ca41c0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.be6c8b24bc0419a7242df6ffada532a17730fe13180fce16a91b310892d6ebad.wasm", - "tx_init_validator.wasm": "tx_init_validator.419c2cd5ddfdcc728ea41903e92ed05e60679086babf0feb8146a1a5c1c7ad79.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.d5dcf0139e3fc332474db7ad9fd74f03af3c50433833a222a8ecf9880faedc1e.wasm", - "tx_transfer.wasm": "tx_transfer.15a74bbc4093bb0fd3e7943f597f88a444d6e7ea6e3a47401430e01945fe9ceb.wasm", - "tx_unbond.wasm": "tx_unbond.64ac67930786fc9d18631ed2d3a225a261114129a9ff57986347c904367efac5.wasm", - "tx_update_vp.wasm": "tx_update_vp.7148b2fef2f9a438ec8e3bf42d1e120ce690f0f69bb2b1c481711ee8b22cef54.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.5488e66b41ea1c45efdb6152fe8897c37e731ae97958db024bf1905651e0f54c.wasm", - "tx_withdraw.wasm": "tx_withdraw.976687bb02cde635a97de500ea72631f37b50516d34f72d5da3ca82b9617fe57.wasm", - "vp_nft.wasm": "vp_nft.92e1c20e54e67a8baa00bbeb61b3712cca32f34bd7e63e4f7f5da23bc303529a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ddbda2d7f226d40a337eb5176f96050501996d6db8f71fa99c21382f6a631b41.wasm", - "vp_token.wasm": "vp_token.60879bfd767808fe6400096cb1527fe44c81e1893a4ff9ce593a3d36e89a45f6.wasm", - "vp_user.wasm": "vp_user.a51b5650c3303789857d4af18d1d4b342bfa5974fcb2b8d6eca906be998168c5.wasm" + "tx_bond.wasm": "tx_bond.72f7ca706910728e7cd2699d225147634981f2bd82fa5c5e1800f33dd7a9268f.wasm", + "tx_ibc.wasm": "tx_ibc.cf61f60f726b00c4e4e26a2bfbb54d5a9fb0503aeb7ae46d9cfcd269417c6de4.wasm", + "tx_init_account.wasm": "tx_init_account.be35e9136ce7c62236ef40a0ec3a4fbfdd1c1c5999b0943c0495895c574ac01b.wasm", + "tx_init_nft.wasm": "tx_init_nft.b8dd99751cf701dcc04ccdd795a37c84ad6e37c833cf2d83ca674b1a5b8b7246.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.da041bb1412b6d4bb303232aaf6bec9369138d4a94b70e5b2e2b87dadb0a47b9.wasm", + "tx_init_validator.wasm": "tx_init_validator.628232b3c034a63d11bb6b75be4f4ed831c41cacf1b710ee7cb6fd94d889d12e.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9bccf7930e21c59a03ff0aa7c85210bec8a320a87ed3d9c4bf000f98ade0cea2.wasm", + "tx_transfer.wasm": "tx_transfer.22f49259ce8c1534473959d699bbbfecb5b42499e9752785aa597c54f059e54b.wasm", + "tx_unbond.wasm": "tx_unbond.197405a2903fc1bf4a1b8f4bb2d901b9b0c455443d567907bd317d756afb16a5.wasm", + "tx_update_vp.wasm": "tx_update_vp.bb01d77ae24013ba7652c723bb4e446607b34dff10e4f01de4a6640aa80d282a.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.55f84360fc7f4cec4542e53272017ecae22e004bac0faf62550c8711895bbae5.wasm", + "tx_withdraw.wasm": "tx_withdraw.69dfa7f299a28ce25190402b231d2dd184431c5c3b9a691aae7b77a366c6d78b.wasm", + "vp_nft.wasm": "vp_nft.8234618f0a3de3d7a6dd75d1463d42a50a357b9783a83525c0093297a0b69738.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.92e4bb1ac583963ebe69a818d670c72e0db2370fe7a5ab2216060603f8e18440.wasm", + "vp_token.wasm": "vp_token.34405f1e1568f6478606de9cd8bb3ff1ffb78f1aa14cfc32861b1c2cf4b6eddd.wasm", + "vp_user.wasm": "vp_user.b70ceb1616f51aae27672c1d4c1705392716dca185e0503d61b3457c4e773f78.wasm" } \ No newline at end of file