From 3ee233a46a7ba1b46a63430a3baa0421b88bc635 Mon Sep 17 00:00:00 2001 From: jacksoom Date: Mon, 25 Sep 2023 10:31:52 +0800 Subject: [PATCH] feat(grpc_client): Add broadcast transaction --- grpc-client/Cargo.lock | 1 + grpc-client/Cargo.toml | 3 +- grpc-client/src/cli.rs | 97 ++++++++++++++++++++++++++++++++++++++++ grpc-client/src/tests.rs | 97 ++++++---------------------------------- 4 files changed, 114 insertions(+), 84 deletions(-) diff --git a/grpc-client/Cargo.lock b/grpc-client/Cargo.lock index 8db2a0e..f99926c 100644 --- a/grpc-client/Cargo.lock +++ b/grpc-client/Cargo.lock @@ -651,6 +651,7 @@ dependencies = [ "log", "prost-types", "rand_core", + "signature", "tokio", "tonic", ] diff --git a/grpc-client/Cargo.toml b/grpc-client/Cargo.toml index af582a0..7e1f27a 100644 --- a/grpc-client/Cargo.toml +++ b/grpc-client/Cargo.toml @@ -13,6 +13,7 @@ bip32 = "0.5.1" tonic = { version = "0.9.0", optional = true, default-features = false, features = ["codegen", "prost", "tls", "tls-roots"] } cosmrs = "0.14.0" prost-types = "0.11" +signature = "2.1.0" [features] default = ["grpc-transport"] @@ -21,4 +22,4 @@ grpc-transport = ["grpc", "tonic/transport"] cosmwasm = [] [dev-dependencies] -log = "0.4.20" # A lightweight logging facade for Rust \ No newline at end of file +log = "0.4.20" # A lightweight logging facade for Rust diff --git a/grpc-client/src/cli.rs b/grpc-client/src/cli.rs index 9b3b426..307c58f 100644 --- a/grpc-client/src/cli.rs +++ b/grpc-client/src/cli.rs @@ -1,3 +1,13 @@ +use cosmrs::crypto::secp256k1::SigningKey; +use gotabit_sdk_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; +use gotabit_sdk_proto::cosmos::base::v1beta1::Coin; +use gotabit_sdk_proto::cosmos::tx::signing::v1beta1::SignMode; +use gotabit_sdk_proto::cosmos::tx::v1beta1::mode_info::{self}; +use gotabit_sdk_proto::cosmos::tx::v1beta1::{ + AuthInfo, BroadcastMode, BroadcastTxRequest, Fee, SignDoc, SignerInfo, Tip, TxBody, TxRaw, +}; +use gotabit_sdk_proto::cosmos::tx::v1beta1::{BroadcastTxResponse, ModeInfo}; +use std::error::Error; use tonic::transport::Channel; use gotabit_sdk_proto::cosmos::{ @@ -31,6 +41,8 @@ use gotabit_sdk_proto::cosmwasm::wasm::v1::{ use crate::networks::NetworkInfo; +use gotabit_sdk_proto::traits::MessageExt; + pub struct Cosmos { pub app: AppCli, pub auth: AuthCli, @@ -121,4 +133,89 @@ impl GrpcClient { }, }) } + + pub async fn broadcast_tx_sync( + &mut self, + sign_key: SigningKey, + addr: String, + msg: T, + coin: Coin, + memo: String, + timeout_height: u64, + gas: u64, + tx_tip: Option, + ) -> Result, Box> + where + T: gotabit_sdk_proto::traits::MessageExt + gotabit_sdk_proto::traits::TypeUrl, + { + // get account seq + let base_account_resp = self + .clients + .cosmos + .auth + .account(QueryAccountRequest { address: addr }) + .await? + .into_inner(); + let base_acct: BaseAccount = + BaseAccount::from_any(&base_account_resp.account.unwrap()).unwrap(); + + // build a simple transfer transction and sign + let tx_body = TxBody { + messages: vec![msg.to_any().unwrap()], + memo, + timeout_height, + extension_options: Default::default(), + non_critical_extension_options: Default::default(), + }; + + // build a single sign + let signer_info = SignerInfo { + public_key: Some(sign_key.public_key().into()), + mode_info: Some(ModeInfo { + sum: Some(mode_info::Sum::Single(mode_info::Single { + mode: SignMode::Direct.into(), + })), + }), + sequence: base_acct.sequence, + }; + + let auth_info = AuthInfo { + signer_infos: vec![signer_info], + fee: Some(Fee { + amount: vec![coin], + gas_limit: gas, + payer: Default::default(), + granter: Default::default(), + }), + tip: tx_tip, + }; + + let sign_doc = SignDoc { + body_bytes: tx_body.to_bytes()?, + auth_info_bytes: auth_info.to_bytes()?, + chain_id: self.chain_id.to_string(), + account_number: base_acct.account_number, + }; + + let sign_doc_bytes = sign_doc.to_bytes()?; + let sign = sign_key.sign(&sign_doc_bytes)?; + + let tx_raw = TxRaw { + body_bytes: sign_doc.body_bytes, + auth_info_bytes: auth_info.to_bytes().unwrap(), + signatures: vec![sign.to_vec()], + }; + + let resp = self + .clients + .cosmos + .tx + .broadcast_tx(BroadcastTxRequest { + tx_bytes: tx_raw.to_bytes().unwrap(), + mode: BroadcastMode::Sync.into(), + }) + .await?; + + Ok(resp) + } } diff --git a/grpc-client/src/tests.rs b/grpc-client/src/tests.rs index e3868a2..6bcc0e5 100644 --- a/grpc-client/src/tests.rs +++ b/grpc-client/src/tests.rs @@ -2,23 +2,13 @@ use crate::{cli::GrpcClient, networks::TEST_NET}; use gotabit_sdk_proto::cosmos::bank::v1beta1::MsgSend; use gotabit_sdk_proto::cosmos::base::v1beta1::Coin; -use gotabit_sdk_proto::cosmos::tx::v1beta1::{ - mode_info, AuthInfo, BroadcastMode, BroadcastTxRequest, Fee, ModeInfo, SignDoc, SignerInfo, - TxBody, TxRaw, -}; use cosmrs::crypto::secp256k1; -use gotabit_sdk_proto::cosmos::tx::signing::v1beta1::SignMode; +use gotabit_sdk_proto::cosmos::bank::v1beta1::QueryBalanceRequest; use gotabit_sdk_proto::cosmwasm::wasm::v1::QueryContractInfoRequest; -use gotabit_sdk_proto::{ - cosmos::auth::v1beta1::BaseAccount, cosmos::auth::v1beta1::QueryAccountRequest, - cosmos::bank::v1beta1::QueryBalanceRequest, traits::MessageExt, -}; use bip32::{Mnemonic, XPrv}; -use gotabit_sdk_proto::cosmos::auth::v1beta1::BaseAccount as BaseAcct; - const GIO_PREFIX: &'static str = "gio"; const TEST_NET_MNEMONIC: &'static str = "nose enjoy rare comic champion cancel axis chronic fringe promote shield own twenty lab decline chat light stamp open pet salon lyrics mimic pride"; @@ -71,86 +61,27 @@ async fn test_submit_tx() { // create grpc client by given address let mut client = GrpcClient::new(&TEST_NET).await.unwrap(); - // query sender base_account infomation - let resp = client - .clients - .cosmos - .auth - .account(QueryAccountRequest { - address: sender_account_id.to_string(), - }) - .await - .unwrap() - .into_inner(); - - let base_acct: BaseAccount = BaseAcct::from_any(&resp.account.unwrap()).unwrap(); - - // Transaction metadata: chain, account, sequence, gas, fee, timeout, and memo. let gas = 100_000u64; let timeout_height = 11358142; let memo = "example memo".to_string(); - // build a simple transfer transction and sign - let tx_body = TxBody { - messages: vec![msg_send.to_any().unwrap()], - memo, - timeout_height, - extension_options: Default::default(), - non_critical_extension_options: Default::default(), - }; - // build a single sign - let signer_info = SignerInfo { - public_key: Some(sender_public_key.into()), - mode_info: Some(ModeInfo { - sum: Some(mode_info::Sum::Single(mode_info::Single { - mode: SignMode::Direct.into(), - })), - }), - sequence: base_acct.sequence, - }; - - let auth_info = AuthInfo { - signer_infos: vec![signer_info], - fee: Some(Fee { - amount: vec![coin], - gas_limit: gas, - payer: Default::default(), - granter: Default::default(), - }), - tip: None, - }; - - let sign_doc = SignDoc { - body_bytes: tx_body.to_bytes().unwrap(), - auth_info_bytes: auth_info.to_bytes().unwrap(), - chain_id: client.chain_id.to_string(), - account_number: base_acct.account_number, - }; - - let sign_doc_bytes = sign_doc.to_bytes().unwrap(); - let sign = sender_private_key.sign(&sign_doc_bytes).unwrap(); - - let tx_raw = TxRaw { - body_bytes: sign_doc.body_bytes, - auth_info_bytes: auth_info.to_bytes().unwrap(), - signatures: vec![sign.to_vec()], - }; - // broadcast signed transaction let resp = client - .clients - .cosmos - .tx - .broadcast_tx(BroadcastTxRequest { - tx_bytes: tx_raw.to_bytes().unwrap(), - mode: BroadcastMode::Sync.into(), - }) + .broadcast_tx_sync( + sender_private_key, + sender_account_id.to_string(), + msg_send, + coin, + memo, + timeout_height, + gas, + None, + ) .await - .unwrap() - .into_inner() - .tx_response .unwrap(); - assert_eq!(resp.code, 0); + let response_body = resp.into_inner().tx_response.unwrap(); + + assert_eq!(response_body.code, 0); } #[tokio::test]