Skip to content

Commit

Permalink
Added CosmosSigner default implementation (#494)
Browse files Browse the repository at this point in the history
* Added CosmosSigner default implementation

* Moved broadcast to sender

* Renamed CosmosSigneré

* Nit

* Nits

* Renamed to account_id

* Update cw-orch-daemon/src/senders/sign.rs

Co-authored-by: CyberHoward <88450409+CyberHoward@users.noreply.github.com>

---------

Co-authored-by: CyberHoward <88450409+CyberHoward@users.noreply.github.com>
  • Loading branch information
Kayanski and CyberHoward authored Oct 1, 2024
1 parent d5afc96 commit f2cd8b6
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 140 deletions.
177 changes: 76 additions & 101 deletions cw-orch-daemon/src/senders/cosmos.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use super::{cosmos_options::CosmosWalletKey, query::QuerySender, tx::TxSender};
use super::{
cosmos_options::CosmosWalletKey,
query::QuerySender,
sign::{Signer, SigningAccount},
tx::TxSender,
};
use crate::{
core::parse_cw_coins,
cosmos_modules::{self, auth::BaseAccount},
Expand All @@ -7,10 +12,6 @@ use crate::{
keys::private::PrivateKey,
proto::injective::{InjectiveEthAccount, ETHEREUM_COIN_TYPE},
queriers::{Bank, Node},
tx_broadcaster::{
account_sequence_strategy, assert_broadcast_code_cosm_response, insufficient_fee_strategy,
TxBroadcaster,
},
tx_builder::TxBuilder,
tx_resp::CosmTxResponse,
upload_wasm, CosmosOptions, GrpcChannel,
Expand All @@ -20,9 +21,9 @@ use cosmos_modules::vesting::PeriodicVestingAccount;
use cosmrs::{
bank::MsgSend,
crypto::secp256k1::SigningKey,
proto::{cosmos::authz::v1beta1::MsgExec, traits::Message},
proto::traits::Message,
tendermint::chain::Id,
tx::{self, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo, SignerPublicKey},
tx::{self, Fee, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo, SignerPublicKey},
AccountId, Any,
};
use cosmwasm_std::{coin, Addr, Coin};
Expand Down Expand Up @@ -159,23 +160,7 @@ impl Wallet {
}

pub fn pub_addr_str(&self) -> String {
self.account_id().to_string()
}

pub async fn broadcast_tx(
&self,
tx: Raw,
) -> Result<cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse, DaemonError> {
let mut client = cosmos_modules::tx::service_client::ServiceClient::new(self.channel());
let commit = client
.broadcast_tx(cosmos_modules::tx::BroadcastTxRequest {
tx_bytes: tx.to_bytes()?,
mode: cosmos_modules::tx::BroadcastMode::Sync.into(),
})
.await?;

let commit = commit.into_inner().tx_response.unwrap();
Ok(commit)
Signer::account_id(self).to_string()
}

pub async fn bank_send(
Expand Down Expand Up @@ -269,21 +254,6 @@ impl Wallet {
self.commit_tx_any(msgs, memo).await
}

pub fn sign(&self, sign_doc: SignDoc) -> Result<Raw, DaemonError> {
let tx_raw = if self.private_key.coin_type == ETHEREUM_COIN_TYPE {
#[cfg(not(feature = "eth"))]
panic!(
"Coin Type {} not supported without eth feature",
ETHEREUM_COIN_TYPE
);
#[cfg(feature = "eth")]
self.private_key.sign_injective(sign_doc)?
} else {
sign_doc.sign(&self.cosmos_private_key())?
};
Ok(tx_raw)
}

pub async fn base_account(&self) -> Result<BaseAccount, DaemonError> {
let addr = self.address().to_string();

Expand Down Expand Up @@ -433,47 +403,79 @@ impl QuerySender for Wallet {
}
}

impl TxSender for Wallet {
async fn commit_tx_any(
&self,
msgs: Vec<Any>,
memo: Option<&str>,
) -> Result<CosmTxResponse, DaemonError> {
let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64;
fn get_mnemonic_env(chain_kind: &ChainKind) -> Result<String, CwEnvError> {
match chain_kind {
ChainKind::Local => DaemonEnvVars::local_mnemonic(),
ChainKind::Testnet => DaemonEnvVars::test_mnemonic(),
ChainKind::Mainnet => DaemonEnvVars::main_mnemonic(),
_ => None,
}
.ok_or(CwEnvError::EnvVarNotPresentNamed(
get_mnemonic_env_name(chain_kind).to_string(),
))
}

fn get_mnemonic_env_name(chain_kind: &ChainKind) -> &str {
match chain_kind {
ChainKind::Local => LOCAL_MNEMONIC_ENV_NAME,
ChainKind::Testnet => TEST_MNEMONIC_ENV_NAME,
ChainKind::Mainnet => MAIN_MNEMONIC_ENV_NAME,
_ => panic!("Can't set mnemonic for unspecified chainkind"),
}
}

let msgs = if self.options.authz_granter.is_some() {
// We wrap authz messages
vec![Any {
type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(),
value: MsgExec {
grantee: self.pub_addr_str(),
msgs,
}
.encode_to_vec(),
}]
impl Signer for Wallet {
fn sign(&self, sign_doc: SignDoc) -> Result<Raw, DaemonError> {
let tx_raw = if self.private_key.coin_type == ETHEREUM_COIN_TYPE {
#[cfg(not(feature = "eth"))]
panic!(
"Coin Type {} not supported without eth feature",
ETHEREUM_COIN_TYPE
);
#[cfg(feature = "eth")]
self.private_key.sign_injective(sign_doc)?
} else {
msgs
sign_doc.sign(&self.cosmos_private_key())?
};
Ok(tx_raw)
}

let tx_body = TxBuilder::build_body(msgs, memo, timeout_height);
fn chain_id(&self) -> String {
self.chain_info.chain_id.clone()
}

let tx_builder = TxBuilder::new(tx_body);
fn signer_info(&self, sequence: u64) -> SignerInfo {
SignerInfo {
public_key: self.private_key.get_signer_public_key(&self.secp),
mode_info: ModeInfo::single(SignMode::Direct),
sequence,
}
}

fn build_fee(&self, amount: impl Into<u128>, gas_limit: u64) -> Result<Fee, DaemonError> {
TxBuilder::build_fee(
amount,
&self.get_fee_token(),
gas_limit,
self.options.fee_granter.clone(),
)
}

// We retry broadcasting the tx, with the following strategies
// 1. In case there is an `incorrect account sequence` error, we can retry as much as possible (doesn't cost anything to the user)
// 2. In case there is an insufficient_fee error, we retry once (costs fee to the user everytime we submit this kind of tx)
// 3. In case there is an other error, we fail
let tx_response = TxBroadcaster::default()
.add_strategy(insufficient_fee_strategy())
.add_strategy(account_sequence_strategy())
.broadcast(tx_builder, self)
.await?;
async fn signing_account(&self) -> Result<super::sign::SigningAccount, DaemonError> {
let BaseAccount {
account_number,
sequence,
..
} = self.base_account().await?;

let resp = Node::new_async(self.channel())
._find_tx(tx_response.txhash)
.await?;
Ok(SigningAccount {
account_number,
sequence,
})
}

assert_broadcast_code_cosm_response(resp)
fn gas_price(&self) -> Result<f64, DaemonError> {
Ok(self.chain_info.gas_price)
}

fn account_id(&self) -> AccountId {
Expand All @@ -485,34 +487,7 @@ impl TxSender for Wallet {
.unwrap()
}

/// Actual sender of the messages.
/// This is different when using authz capabilites
fn msg_sender(&self) -> Result<AccountId, DaemonError> {
if let Some(sender) = &self.options.authz_granter {
Ok(sender.as_str().parse()?)
} else {
Ok(self.account_id())
}
}
}

fn get_mnemonic_env(chain_kind: &ChainKind) -> Result<String, CwEnvError> {
match chain_kind {
ChainKind::Local => DaemonEnvVars::local_mnemonic(),
ChainKind::Testnet => DaemonEnvVars::test_mnemonic(),
ChainKind::Mainnet => DaemonEnvVars::main_mnemonic(),
_ => None,
}
.ok_or(CwEnvError::EnvVarNotPresentNamed(
get_mnemonic_env_name(chain_kind).to_string(),
))
}

fn get_mnemonic_env_name(chain_kind: &ChainKind) -> &str {
match chain_kind {
ChainKind::Local => LOCAL_MNEMONIC_ENV_NAME,
ChainKind::Testnet => TEST_MNEMONIC_ENV_NAME,
ChainKind::Mainnet => MAIN_MNEMONIC_ENV_NAME,
_ => panic!("Can't set mnemonic for unspecified chainkind"),
fn authz_granter(&self) -> Option<&Addr> {
self.options.authz_granter.as_ref()
}
}
1 change: 1 addition & 0 deletions cw-orch-daemon/src/senders/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Core Sender traits
pub mod builder;
pub mod query;
pub mod sign;
pub mod tx;

// Senders
Expand Down
135 changes: 135 additions & 0 deletions cw-orch-daemon/src/senders/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::{
queriers::Node,
tx_broadcaster::{
account_sequence_strategy, assert_broadcast_code_cosm_response, insufficient_fee_strategy,
TxBroadcaster,
},
CosmTxResponse, DaemonError, QuerySender, TxBuilder, TxSender,
};
use cosmrs::{
proto::cosmos::authz::v1beta1::MsgExec,
tendermint::chain::Id,
tx::{Body, Fee, Raw, SignDoc, SignerInfo},
AccountId, Any,
};
use cosmwasm_std::Addr;
use prost::Message;

pub struct SigningAccount {
pub account_number: u64,
pub sequence: u64,
}

pub trait Signer: QuerySender<Error = DaemonError> + Sync {
// --- General information about the signer --- //
/// The chain id of the connected chain
fn chain_id(&self) -> String;

/// The account id of the signer.
fn account_id(&self) -> AccountId;

fn signing_account(
&self,
) -> impl std::future::Future<Output = Result<SigningAccount, DaemonError>> + Send;

/// Signals wether this signer is using authz
/// If set to true, the signed messages will be wrapped inside authz messages
fn authz_granter(&self) -> Option<&Addr> {
None
}

// --- Related to transaction signing --- //
/// Transaction signing
fn sign(&self, sign_doc: SignDoc) -> Result<Raw, DaemonError>;

fn signer_info(&self, sequence: u64) -> SignerInfo;

fn build_fee(&self, amount: impl Into<u128>, gas_limit: u64) -> Result<Fee, DaemonError>;

fn gas_price(&self) -> Result<f64, DaemonError>;

/// Computes the gas needed for submitting a transaction
fn calculate_gas(
&self,
tx_body: &Body,
sequence: u64,
account_number: u64,
) -> impl std::future::Future<Output = Result<u64, DaemonError>> + Send {
async move {
let fee = self.build_fee(0u8, 0)?;

let auth_info = self.signer_info(sequence).auth_info(fee);

let sign_doc = SignDoc::new(
tx_body,
&auth_info,
&Id::try_from(self.chain_id())?,
account_number,
)?;

let tx_raw = self.sign(sign_doc)?;

Node::new_async(self.channel())
._simulate_tx(tx_raw.to_bytes()?)
.await
}
}
}

impl<T: Signer + Sync> TxSender for T {
fn account_id(&self) -> cosmrs::AccountId {
self.account_id()
}

async fn commit_tx_any(
&self,
msgs: Vec<Any>,
memo: Option<&str>,
) -> Result<CosmTxResponse, DaemonError> {
let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64;

let msgs = if self.authz_granter().is_some() {
// We wrap authz messages
vec![Any {
type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(),
value: MsgExec {
grantee: self.account_id().to_string(),
msgs,
}
.encode_to_vec(),
}]
} else {
msgs
};

let tx_body = TxBuilder::build_body(msgs, memo, timeout_height);

let tx_builder = TxBuilder::new(tx_body);

// We retry broadcasting the tx, with the following strategies
// 1. In case there is an `incorrect account sequence` error, we can retry as much as possible (doesn't cost anything to the user)
// 2. In case there is an insufficient_fee error, we retry once (costs fee to the user everytime we submit this kind of tx)
// 3. In case there is an other error, we fail

let tx_response = TxBroadcaster::default()
.add_strategy(insufficient_fee_strategy())
.add_strategy(account_sequence_strategy())
.broadcast(tx_builder, self)
.await?;

let resp = Node::new_async(self.channel())
._find_tx(tx_response.txhash)
.await?;

assert_broadcast_code_cosm_response(resp)
}
/// Actual sender of the messages.
/// This is different when using authz capabilites
fn msg_sender(&self) -> Result<AccountId, DaemonError> {
if let Some(sender) = self.authz_granter() {
Ok(sender.as_str().parse()?)
} else {
Ok(self.account_id())
}
}
}
Loading

0 comments on commit f2cd8b6

Please sign in to comment.