From cfc253c28e7e0dcdec5b3a83e240da0d7797efdc Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Fri, 16 Dec 2022 14:33:47 +0100 Subject: [PATCH] feat: implement asyn client trait --- .../slices/StakingAndGovernance/actions.ts | 10 +- apps/namada-interface/src/slices/transfers.ts | 4 +- packages/shared/lib/Cargo.lock | 72 ++---- packages/shared/lib/Cargo.toml | 4 +- packages/shared/lib/src/lib.rs | 6 +- packages/shared/lib/src/query.rs | 104 ++++++++ packages/shared/lib/src/rpc_client.rs | 230 +++++++----------- 7 files changed, 222 insertions(+), 208 deletions(-) create mode 100644 packages/shared/lib/src/query.rs diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts index 093a1d0c1a..bf12cce4ba 100644 --- a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts +++ b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts @@ -14,7 +14,7 @@ import { } from "./types"; import { myStakingData } from "./fakeData"; import { RootState } from "store"; -import { Abci } from "@anoma/shared"; +import { Query } from "@anoma/shared"; import { RpcClient } from "@anoma/rpc"; import { fetchWasmCode } from "@anoma/utils"; import { SignedTx, Signer, Tokens, TxWasm } from "@anoma/types"; @@ -83,8 +83,8 @@ export const fetchValidators = createAsyncThunk< const { chainId } = thunkApi.getState().settings; const { rpc } = chains[chainId]; - const abci = new Abci(rpc); - const allValidators = (await abci.query_all_validators()).map(toValidator); + const query = new Query(rpc); + const allValidators = (await query.query_all_validators()).map(toValidator); thunkApi.dispatch(fetchMyValidators(allValidators)); return Promise.resolve({ allValidators }); @@ -120,8 +120,8 @@ export const fetchMyValidators = createAsyncThunk< const accounts = thunkApi.getState().accounts.derived[chainId]; const addresses = Object.keys(accounts); - const abci = new Abci(rpc); - const myValidatorsRes = await abci.query_my_validators(addresses); + const query = new Query(rpc); + const myValidatorsRes = await query.query_my_validators(addresses); const myValidators = myValidatorsRes.reduce(toMyValidators, []); const myStakingPositions = myValidatorsRes.map(toStakingPosition); diff --git a/apps/namada-interface/src/slices/transfers.ts b/apps/namada-interface/src/slices/transfers.ts index 38d9af369d..e213720d99 100644 --- a/apps/namada-interface/src/slices/transfers.ts +++ b/apps/namada-interface/src/slices/transfers.ts @@ -17,6 +17,7 @@ import { } from "slices/notifications"; import { fetchBalanceByToken } from "./balances"; import { getIntegration } from "services"; +import { Query } from "@anoma/shared"; enum Toasts { TransferStarted, @@ -362,6 +363,7 @@ export const submitTransferTransaction = createAsyncThunk( const { rpc } = chains[chainId]; const rpcClient = new RpcClient(rpc); + const query = new Query(rpc); notify && dispatch( @@ -379,7 +381,7 @@ export const submitTransferTransaction = createAsyncThunk( }; try { - const epoch = await rpcClient.queryEpoch(); + const epoch = await query.query_epoch(); const revealPk = await revealPublicKey( account, token.address || "", diff --git a/packages/shared/lib/Cargo.lock b/packages/shared/lib/Cargo.lock index 897e9c53fd..b413d7ac1d 100644 --- a/packages/shared/lib/Cargo.lock +++ b/packages/shared/lib/Cargo.lock @@ -1290,9 +1290,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "time 0.3.17", "tracing", ] @@ -1307,7 +1307,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", ] [[package]] @@ -1603,8 +1603,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" version = "0.11.0" -source = "git+https://github.com/anoma/namada?tag=v0.11.0#7ed315a96cd5bfc8e0e2bef37e065746eada6622" +source = "git+https://github.com/anoma/namada?branch=mateuszj0110#8d4bc406e14ce261cf55fef5bbcefb411fd8bc6b" dependencies = [ + "async-trait", "bellman", "bls12_381", "borsh", @@ -1624,8 +1625,8 @@ dependencies = [ "rust_decimal", "serde_json", "sha2 0.9.9", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-proto", "thiserror", "tracing", "wasmparser", @@ -1635,7 +1636,7 @@ dependencies = [ [[package]] name = "namada_core" version = "0.11.0" -source = "git+https://github.com/anoma/namada?tag=v0.11.0#7ed315a96cd5bfc8e0e2bef37e065746eada6622" +source = "git+https://github.com/anoma/namada?branch=mateuszj0110#8d4bc406e14ce261cf55fef5bbcefb411fd8bc6b" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1667,8 +1668,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -1678,7 +1679,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" version = "0.11.0" -source = "git+https://github.com/anoma/namada?tag=v0.11.0#7ed315a96cd5bfc8e0e2bef37e065746eada6622" +source = "git+https://github.com/anoma/namada?branch=mateuszj0110#8d4bc406e14ce261cf55fef5bbcefb411fd8bc6b" dependencies = [ "borsh", "derivative", @@ -2463,6 +2464,7 @@ dependencies = [ name = "shared" version = "0.1.0" dependencies = [ + "async-trait", "borsh", "chrono", "getrandom 0.2.8", @@ -2476,7 +2478,6 @@ dependencies = [ "rayon", "serde", "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284)", "thiserror", "wasm-bindgen", "wasm-bindgen-futures", @@ -2590,34 +2591,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284)", - "time 0.3.17", - "zeroize", -] - [[package]] name = "tendermint" version = "0.23.6" @@ -2641,7 +2614,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "time 0.3.17", "zeroize", ] @@ -2655,24 +2628,7 @@ dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "time 0.3.17", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", + "tendermint", "time 0.3.17", ] diff --git a/packages/shared/lib/Cargo.toml b/packages/shared/lib/Cargo.toml index 532eb3cb62..e57ece855d 100644 --- a/packages/shared/lib/Cargo.toml +++ b/packages/shared/lib/Cargo.toml @@ -11,13 +11,14 @@ license = "MIT" crate-type = ["cdylib", "rlib"] [dependencies] +async-trait = {version = "0.1.51"} borsh = "0.9.0" chrono = "0.4.22" getrandom = { version = "0.2.7", features = ["js"] } gloo-utils = { version = "0.1.5", features = ["serde"] } js-sys = "0.3.60" masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -namada = {git = "https://github.com/anoma/namada", tag = "v0.11.0", features = ["ferveo-tpke"]} +namada = {git = "https://github.com/anoma/namada", branch = "mateuszj0110", features = ["ferveo-tpke", "async-client"]} prost = "0.9.0" prost-types = "0.9.0" rand = "0.8.5" @@ -27,7 +28,6 @@ serde_json = "1.0" thiserror = "^1" wasm-bindgen = "0.2.83" wasm-bindgen-futures = "0.4.33" -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} [dependencies.web-sys] version = "0.3.4" diff --git a/packages/shared/lib/src/lib.rs b/packages/shared/lib/src/lib.rs index 737dcf3509..c0e21765de 100644 --- a/packages/shared/lib/src/lib.rs +++ b/packages/shared/lib/src/lib.rs @@ -3,11 +3,11 @@ //! A library of functions to integrate shared functionality from the Anoma ecosystem pub mod account; -pub mod reveal_pk; -pub mod bond; pub mod ibc_transfer; +pub mod query; +pub mod reveal_pk; +pub mod rpc_client; pub mod signer; pub mod transfer; pub mod types; -pub mod rpc_client; mod utils; diff --git a/packages/shared/lib/src/query.rs b/packages/shared/lib/src/query.rs new file mode 100644 index 0000000000..87f426461c --- /dev/null +++ b/packages/shared/lib/src/query.rs @@ -0,0 +1,104 @@ +use gloo_utils::format::JsValueSerdeExt; +use namada::ledger::queries::RPC; +use namada::types::address::Address; +use namada::types::token::Amount; +use serde::Serialize; +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; +use wasm_bindgen::prelude::*; + +use crate::rpc_client::HttpClient; + +#[wasm_bindgen] +pub struct Query { + client: HttpClient, +} + +#[wasm_bindgen] +impl Query { + fn to_js_result(result: T) -> Result + where + T: Serialize, + { + match JsValue::from_serde(&result) { + Ok(v) => Ok(v), + Err(e) => Err(JsError::new(&e.to_string())), + } + } + #[wasm_bindgen(constructor)] + pub fn new(url: String) -> Query { + let client = HttpClient::new(url); + Query { client } + } + + pub async fn query_epoch(&self) -> Result { + let epoch = RPC.shell().epoch(&self.client).await?; + + Query::to_js_result(epoch) + } + + pub async fn query_all_validators(&self) -> Result { + let validator_addresses = RPC + .vp() + .pos() + .validator_addresses(&self.client, &None) + .await?; + + let mut result: Vec<(Address, Amount)> = Vec::new(); + + for address in validator_addresses.into_iter() { + let total_bonds = RPC + .vp() + .pos() + .validator_stake(&self.client, &address, &None) + .await?; + + result.push((address, total_bonds)); + } + + Query::to_js_result(result) + } + + pub async fn query_my_validators( + &self, + owner_addresses: Box<[JsValue]>, + ) -> Result { + let owner_addresses: Vec
= owner_addresses + .into_iter() + .map(|address| { + let address_str = &(address.as_string().unwrap()[..]); + Address::from_str(address_str).unwrap() + }) + .collect(); + + let mut validators_per_address: HashMap> = HashMap::new(); + + for address in owner_addresses.into_iter() { + let validators = RPC.vp().pos().delegations(&self.client, &address).await?; + + validators_per_address.insert(address, validators); + } + + //TODO: Change to Vec of structs + //Owner, Validator, Amount + let mut result: Vec<(Address, Address, Amount)> = Vec::new(); + + for (owner, validators) in validators_per_address.into_iter() { + for validator in validators.into_iter() { + // let bond_path = &format!("/vp/pos/bond_amount/{}/{}", owner, validator)[..]; + // let total_bonds = abci_query::(&self.url, bond_path).await?; + let total_bonds = RPC + .vp() + .pos() + .bond_amount(&self.client, &owner, &validator, &None) + .await?; + + result.push((owner.clone(), validator, total_bonds)); + } + } + + Query::to_js_result(result) + } + + pub async fn broadcast_tx_sync() {} +} diff --git a/packages/shared/lib/src/rpc_client.rs b/packages/shared/lib/src/rpc_client.rs index 2218247714..04fec901ee 100644 --- a/packages/shared/lib/src/rpc_client.rs +++ b/packages/shared/lib/src/rpc_client.rs @@ -1,23 +1,18 @@ -use std::collections::{HashSet, HashMap}; -use std::error::Error; -use std::fmt::{self, Debug, Display, Formatter}; +use gloo_utils::format::JsValueSerdeExt; +use namada::ledger::queries::{Client, EncodedResponseQuery}; +use namada::tendermint::abci::{Code, Log, Path}; +use namada::tendermint::block; +use namada::tendermint::merkle::proof::Proof; +use namada::tendermint::serializers; +use namada::types::storage::BlockHeight; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; use std::str::FromStr; - -use namada::types::address::Address; -use namada::types::token::Amount; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; -use borsh::{BorshDeserialize, BorshSerialize}; -use gloo_utils::format::JsValueSerdeExt; -use serde::{Deserialize, Serialize}; -use tendermint::abci::{Data, Log, Path}; -use tendermint::block; -use tendermint::merkle::proof::Proof; -use tendermint::serializers; - #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct AbciResponse { /// ABCI query results @@ -74,31 +69,14 @@ pub struct AbciQueryResult { pub result: AbciResponse, } -#[derive(Debug, Clone, PartialEq)] -pub struct FetchError { - err: JsValue, -} - -impl Display for FetchError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(&self.err, f) - } -} -impl Error for FetchError {} - -impl From for FetchError { - fn from(value: JsValue) -> Self { - Self { err: value } - } +#[derive(Deserialize, Serialize)] +struct AbciParams { + path: Path, + data: Option>, + height: Option, + prove: bool, } -#[derive(Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] -pub struct Epoch(u64); - -type Prove = bool; - -type AbciParams = (Path, Data, block::Height, Prove); - #[derive(Deserialize, Serialize)] pub struct AbciRequest { id: String, @@ -107,120 +85,94 @@ pub struct AbciRequest { params: AbciParams, } -pub fn create_json_rpc_request(abci_params: AbciParams) -> AbciRequest { - AbciRequest { - id: "".to_owned(), - jsonrpc: "2.0".to_owned(), - method: "abci_query".to_owned(), - params: abci_params, - } -} - -pub async fn fetch(url: &str, method: &str, body: &str) -> Result { - let mut opts = RequestInit::new(); - opts.method(method); - opts.mode(RequestMode::Cors); - opts.body(Some(&JsValue::from(body))); - - let request = Request::new_with_str_and_init(url, &opts)?; - let window = web_sys::window().unwrap(); - let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; - - let resp: Response = resp_value.dyn_into()?; - JsFuture::from(resp.json().unwrap()).await -} - -pub async fn abci_query(url: &str, path: &str) -> Result -where - T: BorshDeserialize + Serialize, -{ - let path = Path::from_str(path).unwrap(); - let data = Data::from(Vec::new()); - let height = block::Height::from(0 as u8); - - let json_rpc_request = create_json_rpc_request((path, data, height, false)); - let body = serde_json::to_string(&json_rpc_request).unwrap(); - - let json = fetch(url, "POST", &body[..]).await?; - let abci_response: AbciQueryResult = JsValue::into_serde(&json).unwrap(); - - match T::try_from_slice(&abci_response.result.response.value[..]) { - Ok(v) => Ok(v), - Err(e) => Err(JsValue::from(e.to_string())), - } -} - -fn to_js_result(result: T) -> Result -where - T: Serialize, -{ - match JsValue::from_serde(&result) { - Ok(v) => Ok(v), - Err(e) => Err(JsValue::from(e.to_string())), - } -} - -#[wasm_bindgen] -#[derive(Serialize, Deserialize)] -pub struct Abci { +pub struct HttpClient { url: String, } -type Owner = Address; -type Validator = Address; - -#[wasm_bindgen] -impl Abci { - #[wasm_bindgen(constructor)] - pub fn new(url: String) -> Abci { - Abci { url } +impl HttpClient { + pub fn new(url: String) -> HttpClient { + HttpClient { url } } - pub async fn query_all_validators(&self) -> Result { - let validator_addresses = - abci_query::>(&self.url, "/vp/pos/validator/addresses").await?; - - let mut result: Vec<(Validator, Amount)> = Vec::new(); - - for address in validator_addresses.into_iter() { - let stake = &format!("/vp/pos/validator/stake/{}", address)[..]; - let total_bonds = abci_query::(&self.url, stake).await?; - - result.push((address, total_bonds)); + fn create_json_rpc_request(&self, abci_params: AbciParams) -> AbciRequest { + AbciRequest { + id: "".to_owned(), + jsonrpc: "2.0".to_owned(), + method: "abci_query".to_owned(), + params: abci_params, } - - to_js_result(result) } - pub async fn query_my_validators(&self, owner_addresses: Box<[JsValue]>) -> Result { - let owner_addresses: Vec
= owner_addresses - .into_iter() - .map(|address| { - let address_str = &(address.as_string().unwrap()[..]); - Address::from_str(address_str).unwrap() - }).collect(); - - let mut validators_per_address: HashMap> = HashMap::new(); + async fn fetch(&self, url: &str, method: &str, body: &str) -> Result { + let mut opts = RequestInit::new(); + opts.method(method); + opts.mode(RequestMode::Cors); + opts.body(Some(&JsValue::from(body))); - for address in owner_addresses.into_iter() { - let validators = abci_query::>( - &self.url, - &format!("/vp/pos/delegations/{}", address.encode())[..]).await?; + let request = Request::new_with_str_and_init(url, &opts)?; + let window = web_sys::window().expect("Window object does not exist"); + let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; - validators_per_address.insert(address, validators); - } - - let mut result: Vec<(Owner, Validator, Amount)> = Vec::new(); + let resp: Response = resp_value.dyn_into()?; + JsFuture::from(resp.json().unwrap()).await + } - for (owner, validators) in validators_per_address.into_iter() { - for validator in validators.into_iter() { - let bond_path = &format!("/vp/pos/bond_amount/{}/{}", owner, validator)[..]; - let total_bonds = abci_query::(&self.url, bond_path).await?; + pub async fn abci_query( + &self, + url: &str, + path: &str, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let path = Path::from_str(path)?; + let json_rpc_request = &self.create_json_rpc_request(AbciParams { + path, + data, + height, + prove, + }); + + let body = serde_json::to_string(&json_rpc_request)?; + + let json = &self.fetch(url, "POST", &body[..]).await.map_err(|e| { + // We are serializing the JsValue to pass it as a string to the Error + // it is a bit meh, but we do not know exact shape of JsValue + let error_js_str = js_sys::JSON::stringify(&e).expect("JsValue to be serializable"); + let error_str: String = error_js_str.into(); + JsError::new(&error_str) + })?; + + let abci_response: AbciQueryResult = JsValue::into_serde(&json)?; + + Ok(abci_response.result.response) + } +} - result.push((owner.clone(), validator, total_bonds)); - } +#[async_trait::async_trait(?Send)] +impl Client for HttpClient { + type Error = JsError; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let response = &self + .abci_query(&self.url, &path, data, height, prove) + .await?; + let response = response.clone(); + let code = Code::from(response.code); + + match code { + Code::Ok => Ok(EncodedResponseQuery { + data: response.value, + info: response.info, + proof: response.proof, + }), + Code::Err(code) => Err(JsError::new(&format!("Error code {}", code))), } - - to_js_result(result) } }