diff --git a/ampd/src/broadcaster.rs b/ampd/src/broadcaster.rs index dcdbea597..9cfc43bca 100644 --- a/ampd/src/broadcaster.rs +++ b/ampd/src/broadcaster.rs @@ -1,18 +1,20 @@ +use std::convert::TryInto; use std::thread; use std::time::Duration; use async_trait::async_trait; use cosmos_sdk_proto::cosmos::base::abci::v1beta1::TxResponse; use cosmos_sdk_proto::cosmos::tx::v1beta1::{ - BroadcastMode, BroadcastTxRequest, GetTxRequest, GetTxResponse, SimulateRequest, TxRaw, + BroadcastMode, BroadcastTxRequest, GetTxRequest, GetTxResponse, SimulateRequest, }; use cosmos_sdk_proto::traits::MessageExt; use cosmrs::tendermint::chain::Id; -use cosmrs::tx::{BodyBuilder, Fee, SignDoc, SignerInfo}; +use cosmrs::tx::Fee; use cosmrs::{Coin, Gas}; use derive_builder::Builder; -use error_stack::{FutureExt, IntoReport, IntoReportCompat, Report, Result, ResultExt}; +use error_stack::{FutureExt, IntoReport, Report, Result, ResultExt}; use futures::TryFutureExt; +use k256::sha2::{Digest, Sha256}; use mockall::automock; use serde::Deserialize; use thiserror::Error; @@ -25,19 +27,19 @@ use crate::report::LoggableError; use crate::tofnd::grpc::SharableEcdsaClient; use crate::types::PublicKey; use dec_coin::DecCoin; -use fee::zero_fee; -use key::ECDSASigningKey; + +use self::tx_builder::TxBuilder; pub mod accounts; pub mod clients; mod dec_coin; -mod fee; pub mod key; +mod tx_builder; #[derive(Error, Debug)] pub enum Error { - #[error("tx marshaling failed")] - TxMarshaling, + #[error("failed building tx")] + TxBuilding, #[error("failed to estimate gas")] GasEstimation, #[error("broadcast failed")] @@ -88,12 +90,9 @@ where T: clients::BroadcastClient, { client: T, - #[allow(dead_code)] signer: SharableEcdsaClient, acc_number: u64, acc_sequence: u64, - priv_key: ECDSASigningKey, - #[allow(dead_code)] pub_key: (String, PublicKey), config: Config, } @@ -104,13 +103,40 @@ where T: clients::BroadcastClient + Send + Sync, { async fn broadcast(&mut self, msgs: Vec) -> Result { - let fee = self.estimate_fee(msgs.clone()).await?; - - let tx_bytes = self.create_tx(msgs, fee)?; + let tx = TxBuilder::default() + .msgs(msgs.clone()) + .fee(self.estimate_fee(msgs).await?) + .pub_key(self.pub_key.1) + .acc_sequence(self.acc_sequence) + .build() + .into_report() + .change_context(Error::TxBuilding)? + .sign_with(&self.config.chain_id, self.acc_number, |sign_digest| { + let mut hasher = Sha256::new(); + hasher.update(sign_digest); + + let sign_digest: [u8; 32] = hasher + .finalize() + .to_vec() + .try_into() + .expect("hash size must be 32"); + + self.signer + .sign(self.pub_key.0.as_str(), sign_digest.into(), &self.pub_key.1) + }) + .await + .change_context(Error::TxBuilding)?; + let tx = BroadcastTxRequest { + tx_bytes: tx + .to_bytes() + .into_report() + .change_context(Error::TxBuilding)?, + mode: BroadcastMode::Sync as i32, + }; let response = self .client - .broadcast_tx(tx_bytes) + .broadcast_tx(tx) .change_context(Error::Broadcast) .await?; let TxResponse { @@ -130,7 +156,20 @@ where } async fn estimate_fee(&mut self, msgs: Vec) -> Result { - let sim_tx = self.create_sim_tx(msgs)?; + let sim_tx = TxBuilder::default() + .msgs(msgs) + .zero_fee() + .pub_key(self.pub_key.1) + .acc_sequence(self.acc_sequence) + .build() + .into_report() + .change_context(Error::TxBuilding)? + .with_dummy_sig() + .await + .change_context(Error::TxBuilding)? + .to_bytes() + .into_report() + .change_context(Error::TxBuilding)?; self.estimate_gas(sim_tx).await.map(|gas| { let gas_adj = (gas as f64 * self.config.gas_adjustment) as u64; @@ -165,54 +204,6 @@ where .await } - fn create_sim_tx(&self, msgs: M) -> Result, Error> - where - M: IntoIterator, - { - let body_bytes = BodyBuilder::new() - .msgs(msgs) - .finish() - .into_bytes() - .into_report() - .change_context(Error::TxMarshaling)?; - - let auth_info_bytes = SignerInfo::single_direct(Some(self.pub_key.1), self.acc_sequence) - .auth_info(zero_fee()) - .into_bytes() - .into_report() - .change_context(Error::TxMarshaling)?; - - let raw = TxRaw { - body_bytes, - auth_info_bytes, - // empty signature to pass validation - signatures: vec![vec![0; 64]], - }; - - raw.to_bytes() - .into_report() - .change_context(Error::TxMarshaling) - } - - pub fn create_tx(&self, msgs: M, fee: Fee) -> Result - where - M: IntoIterator, - { - let body = BodyBuilder::new().msgs(msgs).finish(); - let auth_info = - SignerInfo::single_direct(Some(self.pub_key.1), self.acc_sequence).auth_info(fee); - - SignDoc::new(&body, &auth_info, &self.config.chain_id, self.acc_number) - .and_then(|sign_doc| sign_doc.sign(&((&self.priv_key).into()))) - .and_then(|tx| tx.to_bytes()) - .map(|tx| BroadcastTxRequest { - tx_bytes: tx, - mode: BroadcastMode::Sync as i32, - }) - .into_report() - .change_context(Error::TxMarshaling) - } - async fn confirm_tx(&mut self, tx_hash: &str) -> Result<(), Error> { let mut result: Result<(), Status> = Ok(()); @@ -276,284 +267,284 @@ enum ConfirmationResult { Critical(Error), } -#[cfg(test)] -mod tests { - use cosmos_sdk_proto::cosmos::base::abci::v1beta1::{GasInfo, TxResponse}; - use cosmos_sdk_proto::cosmos::tx::v1beta1::{GetTxResponse, SimulateResponse}; - use cosmos_sdk_proto::Any; - use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; - use error_stack::IntoReport; - use tokio::test; - use tonic::Status; - - use crate::broadcaster::clients::MockBroadcastClient; - use crate::broadcaster::key::ECDSASigningKey; - use crate::broadcaster::{BroadcastClient, Broadcaster, Config, Error}; - use crate::tofnd::grpc::{MockEcdsaClient, SharableEcdsaClient}; - - #[test] - async fn gas_estimation_call_failed() { - let mut client = MockBroadcastClient::new(); - client - .expect_simulate() - .returning(|_| Err(Status::unavailable("unavailable service")).into_report()); - let signer = MockEcdsaClient::new(); - - let priv_key = ECDSASigningKey::random(); - let pub_key = priv_key.public_key(); - let mut broadcaster = BroadcastClient { - client, - signer: SharableEcdsaClient::new(signer), - acc_number: 0, - acc_sequence: 0, - priv_key, - pub_key: ("key_uid".into(), pub_key), - config: Config::default(), - }; - let msgs = vec![dummy_msg()]; - - assert!(matches!( - broadcaster - .broadcast(msgs) - .await - .unwrap_err() - .current_context(), - Error::GasEstimation - )); - } - - #[test] - async fn gas_estimation_none_response() { - let mut client = MockBroadcastClient::new(); - - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: None, - result: None, - }) - }); - let signer = MockEcdsaClient::new(); - - let priv_key = ECDSASigningKey::random(); - let pub_key = priv_key.public_key(); - let mut broadcaster = BroadcastClient { - client, - signer: SharableEcdsaClient::new(signer), - acc_number: 0, - acc_sequence: 0, - priv_key, - pub_key: ("key_uid".into(), pub_key), - config: Config::default(), - }; - let msgs = vec![dummy_msg()]; - - assert!(matches!( - broadcaster - .broadcast(msgs) - .await - .unwrap_err() - .current_context(), - Error::GasEstimation - )); - } - - #[test] - async fn broadcast_failed() { - let mut client = MockBroadcastClient::new(); - - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: Some(GasInfo { - gas_wanted: 0, - gas_used: 0, - }), - result: None, - }) - }); - - client - .expect_broadcast_tx() - .returning(|_| Err(Status::aborted("failed")).into_report()); - let signer = MockEcdsaClient::new(); - - let priv_key = ECDSASigningKey::random(); - let pub_key = priv_key.public_key(); - let mut broadcaster = BroadcastClient { - client, - signer: SharableEcdsaClient::new(signer), - acc_number: 0, - acc_sequence: 0, - priv_key, - pub_key: ("key_uid".into(), pub_key), - config: Config::default(), - }; - let msgs = vec![dummy_msg()]; - - assert!(matches!( - broadcaster - .broadcast(msgs) - .await - .unwrap_err() - .current_context(), - Error::Broadcast - )); - } - - #[test] - async fn tx_confirmation_failed() { - let mut client = MockBroadcastClient::new(); - - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: Some(GasInfo { - gas_wanted: 0, - gas_used: 0, - }), - result: None, - }) - }); - - client - .expect_broadcast_tx() - .returning(|_| Ok(TxResponse::default())); - - client - .expect_get_tx() - .times((Config::default().tx_fetch_max_retries + 1) as usize) - .returning(|_| Err(Status::deadline_exceeded("time out")).into_report()); - let signer = MockEcdsaClient::new(); - - let priv_key = ECDSASigningKey::random(); - let pub_key = priv_key.public_key(); - let mut broadcaster = BroadcastClient { - client, - signer: SharableEcdsaClient::new(signer), - acc_number: 0, - acc_sequence: 0, - priv_key, - pub_key: ("key_uid".into(), pub_key), - config: Config::default(), - }; - let msgs = vec![dummy_msg()]; - - assert!(matches!( - broadcaster - .broadcast(msgs) - .await - .unwrap_err() - .current_context(), - Error::TxConfirmation - )); - } - - #[test] - async fn tx_execution_failed() { - let mut client = MockBroadcastClient::new(); - - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: Some(GasInfo { - gas_wanted: 0, - gas_used: 0, - }), - result: None, - }) - }); - - client - .expect_broadcast_tx() - .returning(|_| Ok(TxResponse::default())); - - client.expect_get_tx().times(1).returning(|_| { - Ok(GetTxResponse { - tx_response: Some(TxResponse { - code: 32, - ..TxResponse::default() - }), - ..GetTxResponse::default() - }) - }); - let signer = MockEcdsaClient::new(); - - let priv_key = ECDSASigningKey::random(); - let pub_key = priv_key.public_key(); - let mut broadcaster = BroadcastClient { - client, - signer: SharableEcdsaClient::new(signer), - acc_number: 0, - acc_sequence: 0, - priv_key, - pub_key: ("key_uid".into(), pub_key), - config: Config::default(), - }; - let msgs = vec![dummy_msg()]; - - assert!(matches!( - broadcaster - .broadcast(msgs) - .await - .unwrap_err() - .current_context(), - Error::ExecutionError { - response: TxResponse { code: 32, .. } - } - )); - } - - #[test] - async fn broadcast_confirmed() { - let mut client = MockBroadcastClient::new(); - - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: Some(GasInfo { - gas_wanted: 0, - gas_used: 0, - }), - result: None, - }) - }); - - client - .expect_broadcast_tx() - .returning(|_| Ok(TxResponse::default())); - - client.expect_get_tx().returning(|_| { - Ok(GetTxResponse { - tx_response: Some(TxResponse { - code: 0, - ..TxResponse::default() - }), - ..GetTxResponse::default() - }) - }); - let signer = MockEcdsaClient::new(); - - let priv_key = ECDSASigningKey::random(); - let pub_key = priv_key.public_key(); - let mut broadcaster = BroadcastClient { - client, - signer: SharableEcdsaClient::new(signer), - acc_number: 0, - acc_sequence: 0, - priv_key, - pub_key: ("key_uid".into(), pub_key), - config: Config::default(), - }; - let msgs = vec![dummy_msg()]; - - assert_eq!(broadcaster.acc_sequence, 0); - assert!(broadcaster.broadcast(msgs).await.is_ok()); - assert_eq!(broadcaster.acc_sequence, 1); - } - - fn dummy_msg() -> Any { - MsgSend { - from_address: AccountId::new("", &[1, 2, 3]).unwrap(), - to_address: AccountId::new("", &[4, 5, 6]).unwrap(), - amount: vec![], - } - .to_any() - .unwrap() - } -} +// #[cfg(test)] +// mod tests { +// use cosmos_sdk_proto::cosmos::base::abci::v1beta1::{GasInfo, TxResponse}; +// use cosmos_sdk_proto::cosmos::tx::v1beta1::{GetTxResponse, SimulateResponse}; +// use cosmos_sdk_proto::Any; +// use cosmrs::{bank::MsgSend, tx::Msg, AccountId}; +// use error_stack::IntoReport; +// use tokio::test; +// use tonic::Status; + +// use crate::broadcaster::clients::MockBroadcastClient; +// use crate::broadcaster::key::ECDSASigningKey; +// use crate::broadcaster::{BroadcastClient, Broadcaster, Config, Error}; +// use crate::tofnd::grpc::{MockEcdsaClient, SharableEcdsaClient}; + +// #[test] +// async fn gas_estimation_call_failed() { +// let mut client = MockBroadcastClient::new(); +// client +// .expect_simulate() +// .returning(|_| Err(Status::unavailable("unavailable service")).into_report()); +// let signer = MockEcdsaClient::new(); + +// let priv_key = ECDSASigningKey::random(); +// let pub_key = priv_key.public_key(); +// let mut broadcaster = BroadcastClient { +// client, +// signer: SharableEcdsaClient::new(signer), +// acc_number: 0, +// acc_sequence: 0, +// priv_key, +// pub_key: ("key_uid".into(), pub_key), +// config: Config::default(), +// }; +// let msgs = vec![dummy_msg()]; + +// assert!(matches!( +// broadcaster +// .broadcast(msgs) +// .await +// .unwrap_err() +// .current_context(), +// Error::GasEstimation +// )); +// } + +// #[test] +// async fn gas_estimation_none_response() { +// let mut client = MockBroadcastClient::new(); + +// client.expect_simulate().returning(|_| { +// Ok(SimulateResponse { +// gas_info: None, +// result: None, +// }) +// }); +// let signer = MockEcdsaClient::new(); + +// let priv_key = ECDSASigningKey::random(); +// let pub_key = priv_key.public_key(); +// let mut broadcaster = BroadcastClient { +// client, +// signer: SharableEcdsaClient::new(signer), +// acc_number: 0, +// acc_sequence: 0, +// priv_key, +// pub_key: ("key_uid".into(), pub_key), +// config: Config::default(), +// }; +// let msgs = vec![dummy_msg()]; + +// assert!(matches!( +// broadcaster +// .broadcast(msgs) +// .await +// .unwrap_err() +// .current_context(), +// Error::GasEstimation +// )); +// } + +// #[test] +// async fn broadcast_failed() { +// let mut client = MockBroadcastClient::new(); + +// client.expect_simulate().returning(|_| { +// Ok(SimulateResponse { +// gas_info: Some(GasInfo { +// gas_wanted: 0, +// gas_used: 0, +// }), +// result: None, +// }) +// }); + +// client +// .expect_broadcast_tx() +// .returning(|_| Err(Status::aborted("failed")).into_report()); +// let signer = MockEcdsaClient::new(); + +// let priv_key = ECDSASigningKey::random(); +// let pub_key = priv_key.public_key(); +// let mut broadcaster = BroadcastClient { +// client, +// signer: SharableEcdsaClient::new(signer), +// acc_number: 0, +// acc_sequence: 0, +// priv_key, +// pub_key: ("key_uid".into(), pub_key), +// config: Config::default(), +// }; +// let msgs = vec![dummy_msg()]; + +// assert!(matches!( +// broadcaster +// .broadcast(msgs) +// .await +// .unwrap_err() +// .current_context(), +// Error::Broadcast +// )); +// } + +// #[test] +// async fn tx_confirmation_failed() { +// let mut client = MockBroadcastClient::new(); + +// client.expect_simulate().returning(|_| { +// Ok(SimulateResponse { +// gas_info: Some(GasInfo { +// gas_wanted: 0, +// gas_used: 0, +// }), +// result: None, +// }) +// }); + +// client +// .expect_broadcast_tx() +// .returning(|_| Ok(TxResponse::default())); + +// client +// .expect_get_tx() +// .times((Config::default().tx_fetch_max_retries + 1) as usize) +// .returning(|_| Err(Status::deadline_exceeded("time out")).into_report()); +// let signer = MockEcdsaClient::new(); + +// let priv_key = ECDSASigningKey::random(); +// let pub_key = priv_key.public_key(); +// let mut broadcaster = BroadcastClient { +// client, +// signer: SharableEcdsaClient::new(signer), +// acc_number: 0, +// acc_sequence: 0, +// priv_key, +// pub_key: ("key_uid".into(), pub_key), +// config: Config::default(), +// }; +// let msgs = vec![dummy_msg()]; + +// assert!(matches!( +// broadcaster +// .broadcast(msgs) +// .await +// .unwrap_err() +// .current_context(), +// Error::TxConfirmation +// )); +// } + +// #[test] +// async fn tx_execution_failed() { +// let mut client = MockBroadcastClient::new(); + +// client.expect_simulate().returning(|_| { +// Ok(SimulateResponse { +// gas_info: Some(GasInfo { +// gas_wanted: 0, +// gas_used: 0, +// }), +// result: None, +// }) +// }); + +// client +// .expect_broadcast_tx() +// .returning(|_| Ok(TxResponse::default())); + +// client.expect_get_tx().times(1).returning(|_| { +// Ok(GetTxResponse { +// tx_response: Some(TxResponse { +// code: 32, +// ..TxResponse::default() +// }), +// ..GetTxResponse::default() +// }) +// }); +// let signer = MockEcdsaClient::new(); + +// let priv_key = ECDSASigningKey::random(); +// let pub_key = priv_key.public_key(); +// let mut broadcaster = BroadcastClient { +// client, +// signer: SharableEcdsaClient::new(signer), +// acc_number: 0, +// acc_sequence: 0, +// priv_key, +// pub_key: ("key_uid".into(), pub_key), +// config: Config::default(), +// }; +// let msgs = vec![dummy_msg()]; + +// assert!(matches!( +// broadcaster +// .broadcast(msgs) +// .await +// .unwrap_err() +// .current_context(), +// Error::ExecutionError { +// response: TxResponse { code: 32, .. } +// } +// )); +// } + +// #[test] +// async fn broadcast_confirmed() { +// let mut client = MockBroadcastClient::new(); + +// client.expect_simulate().returning(|_| { +// Ok(SimulateResponse { +// gas_info: Some(GasInfo { +// gas_wanted: 0, +// gas_used: 0, +// }), +// result: None, +// }) +// }); + +// client +// .expect_broadcast_tx() +// .returning(|_| Ok(TxResponse::default())); + +// client.expect_get_tx().returning(|_| { +// Ok(GetTxResponse { +// tx_response: Some(TxResponse { +// code: 0, +// ..TxResponse::default() +// }), +// ..GetTxResponse::default() +// }) +// }); +// let signer = MockEcdsaClient::new(); + +// let priv_key = ECDSASigningKey::random(); +// let pub_key = priv_key.public_key(); +// let mut broadcaster = BroadcastClient { +// client, +// signer: SharableEcdsaClient::new(signer), +// acc_number: 0, +// acc_sequence: 0, +// priv_key, +// pub_key: ("key_uid".into(), pub_key), +// config: Config::default(), +// }; +// let msgs = vec![dummy_msg()]; + +// assert_eq!(broadcaster.acc_sequence, 0); +// assert!(broadcaster.broadcast(msgs).await.is_ok()); +// assert_eq!(broadcaster.acc_sequence, 1); +// } + +// fn dummy_msg() -> Any { +// MsgSend { +// from_address: AccountId::new("", &[1, 2, 3]).unwrap(), +// to_address: AccountId::new("", &[4, 5, 6]).unwrap(), +// amount: vec![], +// } +// .to_any() +// .unwrap() +// } +// } diff --git a/ampd/src/broadcaster/fee.rs b/ampd/src/broadcaster/fee.rs deleted file mode 100644 index e626b6b96..000000000 --- a/ampd/src/broadcaster/fee.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmrs::tx::Fee; -use cosmrs::Coin; - -pub fn zero_fee() -> Fee { - Fee::from_amount_and_gas( - Coin { - denom: "".parse().unwrap(), - amount: 0, - }, - 0u64, - ) -} diff --git a/ampd/src/broadcaster/tx.rs b/ampd/src/broadcaster/tx.rs new file mode 100644 index 000000000..8fab2cba7 --- /dev/null +++ b/ampd/src/broadcaster/tx.rs @@ -0,0 +1,213 @@ +use core::fmt::Debug; + +use cosmrs::tendermint::chain::Id; +use cosmrs::{ + proto::cosmos::tx::v1beta1::TxRaw, + tx::{BodyBuilder, Fee, SignDoc, SignerInfo}, + Any, Coin, +}; +use derive_builder::Builder; +use error_stack::{IntoReport, IntoReportCompat, Result, ResultExt}; +use futures::Future; +use thiserror::Error; + +use crate::types::PublicKey; + +const DUMMY_CHAIN_ID: &str = "dummychainid"; +const DUMMY_ACC_NUMBER: u64 = 0; + +#[derive(Error, Debug)] +pub enum Error { + #[error("tx signing failed")] + Sign, + #[error("tx marshaling failed")] + Marshaling, +} + +#[derive(Builder)] +pub struct Tx +where + M: IntoIterator, +{ + msgs: M, + pub_key: PublicKey, + acc_sequence: u64, + fee: Fee, +} + +impl TxBuilder +where + M: IntoIterator + Clone, +{ + pub fn zero_fee(&mut self) -> &mut Self { + self.fee(Fee::from_amount_and_gas( + Coin { + denom: "".parse().unwrap(), + amount: 0, + }, + 0u64, + )) + } +} + +impl Tx +where + M: IntoIterator, +{ + pub async fn sign_with( + self, + chain_id: &Id, + acc_number: u64, + sign: F, + ) -> Result + where + F: Fn(Vec) -> Fut, + Fut: Future, Err>>, + { + let body = BodyBuilder::new().msgs(self.msgs).finish(); + let auth_info = + SignerInfo::single_direct(Some(self.pub_key), self.acc_sequence).auth_info(self.fee); + let sign_doc = SignDoc::new(&body, &auth_info, chain_id, acc_number) + .into_report() + .change_context(Error::Marshaling)?; + let sign_digest = sign_doc + .clone() + .into_bytes() + .into_report() + .change_context(Error::Marshaling)?; + + let signature = sign(sign_digest).await.change_context(Error::Sign)?; + + Ok(TxRaw { + body_bytes: sign_doc.body_bytes, + auth_info_bytes: sign_doc.auth_info_bytes, + signatures: vec![signature], + }) + } + + pub async fn with_dummy_sig(self) -> Result { + self.sign_with( + &DUMMY_CHAIN_ID + .parse() + .expect("the dummy chain id must be valid"), + DUMMY_ACC_NUMBER, + |_| async { std::result::Result::<_, Error>::Ok(vec![0; 64]).into_report() }, + ) + .await + } +} + +#[cfg(test)] +mod tests { + use cosmos_sdk_proto::Any; + use cosmrs::{ + bank::MsgSend, + bip32::secp256k1::elliptic_curve::rand_core::OsRng, + crypto::secp256k1::SigningKey, + proto::cosmos::tx::v1beta1::TxRaw, + tendermint::chain::Id, + tx::{BodyBuilder, Fee, Msg, SignDoc, SignerInfo}, + AccountId, Coin, + }; + use error_stack::IntoReport; + use k256::ecdsa; + use k256::sha2::{Digest, Sha256}; + use tokio::test; + + use super::{Error, TxBuilder, DUMMY_CHAIN_ID}; + use crate::types::PublicKey; + + #[test] + async fn sign_with_should_produce_the_correct_tx() { + let priv_key = ecdsa::SigningKey::random(&mut OsRng); + let priv_key_bytes = priv_key.to_bytes(); + let pub_key: PublicKey = priv_key.verifying_key().into(); + let acc_number = 100; + let acc_sequence = 1000; + let chain_id: Id = DUMMY_CHAIN_ID.parse().unwrap(); + let msgs = vec![dummy_msg(), dummy_msg(), dummy_msg()]; + + let actual_tx = TxBuilder::default() + .msgs(msgs.clone()) + .zero_fee() + .pub_key(pub_key) + .acc_sequence(acc_sequence) + .build() + .unwrap() + .sign_with(&chain_id, acc_number, |sign_digest| async move { + let mut hasher = Sha256::new(); + hasher.update(sign_digest); + let hash = hasher.finalize(); + + let priv_key = ecdsa::SigningKey::from_bytes(&priv_key_bytes).unwrap(); + let (signature, _) = priv_key.sign_prehash_recoverable(&hash.to_vec()).unwrap(); + + std::result::Result::<_, Error>::Ok(signature.to_vec()).into_report() + }) + .await + .unwrap(); + + let body = BodyBuilder::new().msgs(msgs).finish(); + let auth_info = SignerInfo::single_direct(Some(pub_key), acc_sequence).auth_info( + Fee::from_amount_and_gas( + Coin { + denom: "".parse().unwrap(), + amount: 0, + }, + 0u64, + ), + ); + let sign_doc = SignDoc::new(&body, &auth_info, &chain_id, acc_number).unwrap(); + let expected_tx = sign_doc + .sign(&SigningKey::from_slice(priv_key_bytes.as_slice()).unwrap()) + .unwrap(); + + assert_eq!(actual_tx, expected_tx.into()); + } + + #[test] + async fn with_dummy_sig_should_produce_the_correct_tx() { + let pub_key: PublicKey = ecdsa::SigningKey::random(&mut OsRng).verifying_key().into(); + let acc_sequence = 1000; + let msgs = vec![dummy_msg(), dummy_msg(), dummy_msg()]; + + let actual_tx = TxBuilder::default() + .msgs(msgs.clone()) + .zero_fee() + .pub_key(pub_key) + .acc_sequence(acc_sequence) + .build() + .unwrap() + .with_dummy_sig() + .await + .unwrap(); + + let body = BodyBuilder::new().msgs(msgs).finish(); + let auth_info = SignerInfo::single_direct(Some(pub_key), acc_sequence).auth_info( + Fee::from_amount_and_gas( + Coin { + denom: "".parse().unwrap(), + amount: 0, + }, + 0u64, + ), + ); + let expected_tx = TxRaw { + body_bytes: body.into_bytes().unwrap(), + auth_info_bytes: auth_info.into_bytes().unwrap(), + signatures: vec![vec![0; 64]], + }; + + assert_eq!(actual_tx, expected_tx); + } + + fn dummy_msg() -> Any { + MsgSend { + from_address: AccountId::new("", &[1, 2, 3]).unwrap(), + to_address: AccountId::new("", &[4, 5, 6]).unwrap(), + amount: vec![], + } + .to_any() + .unwrap() + } +} diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 2199746d8..cf28304a6 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -4,7 +4,6 @@ use crate::broadcaster; use crate::evm::{deserialize_evm_chain_configs, EvmChainConfig}; use crate::tofnd::Config as TofndConfig; use crate::url::Url; -use crate::ECDSASigningKey; #[derive(Deserialize, Debug)] #[serde(default)] @@ -15,8 +14,6 @@ pub struct Config { #[serde(deserialize_with = "deserialize_evm_chain_configs")] pub evm_chains: Vec, pub tofnd_config: TofndConfig, - #[serde(with = "hex")] - pub private_key: ECDSASigningKey, pub event_buffer_cap: usize, } @@ -28,7 +25,6 @@ impl Default for Config { broadcast: broadcaster::Config::default(), evm_chains: vec![], tofnd_config: TofndConfig::default(), - private_key: ECDSASigningKey::random(), event_buffer_cap: 100000, } } @@ -36,10 +32,7 @@ impl Default for Config { #[cfg(test)] mod tests { - use cosmrs::bip32::secp256k1::elliptic_curve::rand_core::OsRng; - use super::Config; - use crate::types::PublicKey; use crate::{broadcaster::key::ECDSASigningKey, evm::ChainName}; #[test] @@ -127,22 +120,4 @@ mod tests { assert_eq!(cfg.tofnd_config.party_uid.as_str(), party_uid); assert_eq!(cfg.tofnd_config.key_uid.as_str(), key_uid); } - - #[test] - fn deserialize_private_key() { - let random_key = ecdsa::SigningKey::random(&mut OsRng); - let hex_private_key = hex::encode(random_key.to_bytes()); - let cfg: Config = - toml::from_str(format!("private_key = '{hex_private_key}'").as_str()).unwrap(); - - assert_eq!( - cfg.private_key.public_key(), - PublicKey::from(random_key.verifying_key()) - ) - } - - #[test] - fn fail_deserialize_private_key() { - assert!(toml::from_str::("private_key = 'a invalid key'").is_err()); - } } diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index a511c8ee5..68b09186e 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -12,7 +12,7 @@ use tokio_util::sync::CancellationToken; use tracing::info; use crate::config::Config; -use broadcaster::{accounts::account, key::ECDSASigningKey, Broadcaster}; +use broadcaster::{accounts::account, Broadcaster}; use event_processor::EventProcessor; use event_sub::Event; use evm::EvmChainConfig; @@ -36,6 +36,8 @@ mod tofnd; mod types; mod url; +const PREFIX: &str = "axelar"; + type HandlerStream = Pin> + Send>>; pub async fn run(cfg: Config, state_path: PathBuf) -> Result<(), Error> { @@ -45,7 +47,6 @@ pub async fn run(cfg: Config, state_path: PathBuf) -> Result<(), Error> { broadcast, evm_chains, tofnd_config, - private_key, event_buffer_cap, } = cfg; @@ -76,7 +77,10 @@ pub async fn run(cfg: Config, state_path: PathBuf) -> Result<(), Error> { } }; - let worker = private_key.address(); + let worker = pub_key + .account_id(PREFIX) + .expect("failed to convert to account identifier") + .into(); let account = account(query_client, &worker).await.map_err(Error::new)?; let broadcaster = broadcaster::BroadcastClientBuilder::default() @@ -84,7 +88,6 @@ pub async fn run(cfg: Config, state_path: PathBuf) -> Result<(), Error> { .signer(ecdsa_client.clone()) .acc_number(account.account_number) .acc_sequence(account.sequence) - .priv_key(private_key) .pub_key((tofnd_config.key_uid, pub_key)) .config(broadcast.clone()) .build()