diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8d1b909db06..61fd90ded1f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4035,6 +4035,7 @@ dependencies = [ "config", "convert_case 0.6.0", "derive-new", + "derive_builder", "ed25519-dalek", "ethers", "ethers-prometheus", @@ -4048,6 +4049,7 @@ dependencies = [ "hyperlane-sealevel", "hyperlane-test", "itertools 0.11.0", + "maplit", "paste", "prometheus", "rocksdb", diff --git a/rust/agents/relayer/src/relayer.rs b/rust/agents/relayer/src/relayer.rs index 5ff91466a22..7badb1174a9 100644 --- a/rust/agents/relayer/src/relayer.rs +++ b/rust/agents/relayer/src/relayer.rs @@ -188,9 +188,20 @@ impl BaseAgent for Relayer { .collect(); let mut msg_ctxs = HashMap::new(); + // let mut custom_metrics = HashMap::new(); for destination in &settings.destination_chains { let destination_chain_setup = core.settings.chain_setup(destination).unwrap().clone(); - + let agent_metrics_conf = destination_chain_setup + .agent_metrics_conf("relayer".to_owned()) + .await?; + println!("~~~ agent metrics: {:?}", agent_metrics_conf); + println!("~~~ agent signer: {:?}", destination_chain_setup.signer); + // custom_metrics.insert( + // destination.id(), + // destination_chain_setup + // .metrics(destination) + // .expect("Missing metrics config"), + // ); let transaction_gas_limit: Option = if skip_transaction_gas_limit_for.contains(&destination.id()) { None diff --git a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs index c9d7200117d..a2439f92a37 100644 --- a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs @@ -18,7 +18,7 @@ use tracing::instrument; pub struct CosmosAggregationIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: Box, } impl CosmosAggregationIsm { @@ -28,7 +28,12 @@ impl CosmosAggregationIsm { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), @@ -50,7 +55,7 @@ impl HyperlaneChain for CosmosAggregationIsm { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -63,7 +68,7 @@ impl AggregationIsm for CosmosAggregationIsm { ) -> ChainResult<(Vec, u8)> { let payload = ModulesAndThresholdRequest::new(message); - let data = self.provider.wasm_query(payload, None).await?; + let data = self.provider.grpc().wasm_query(payload, None).await?; let response: ModulesAndThresholdResponse = serde_json::from_slice(&data)?; let modules: ChainResult> = response diff --git a/rust/chains/hyperlane-cosmos/src/interchain_gas.rs b/rust/chains/hyperlane-cosmos/src/interchain_gas.rs index d96bfb0bab6..83044913b40 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_gas.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_gas.rs @@ -22,6 +22,7 @@ use crate::{ pub struct CosmosInterchainGasPaymaster { domain: HyperlaneDomain, address: H256, + provider: CosmosProvider, } impl HyperlaneContract for CosmosInterchainGasPaymaster { @@ -36,7 +37,7 @@ impl HyperlaneChain for CosmosInterchainGasPaymaster { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -49,11 +50,17 @@ impl CosmosInterchainGasPaymaster { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, + provider, }) } } diff --git a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs index 72a0ac984d5..f4c6932cf51 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs @@ -22,7 +22,7 @@ pub struct CosmosInterchainSecurityModule { /// The address of the ISM contract. address: H256, /// The provider for the ISM contract. - provider: Box, + provider: CosmosProvider, } /// The Cosmos Interchain Security Module Implementation. @@ -33,13 +33,17 @@ impl CosmosInterchainSecurityModule { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider: WasmGrpcProvider = - WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -56,7 +60,7 @@ impl HyperlaneChain for CosmosInterchainSecurityModule { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -71,6 +75,7 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { let data = self .provider + .grpc() .wasm_query(QueryIsmGeneralRequest { ism: query }, None) .await?; diff --git a/rust/chains/hyperlane-cosmos/src/lib.rs b/rust/chains/hyperlane-cosmos/src/lib.rs index 82a4a0ece17..92b3a567300 100644 --- a/rust/chains/hyperlane-cosmos/src/lib.rs +++ b/rust/chains/hyperlane-cosmos/src/lib.rs @@ -12,6 +12,7 @@ mod interchain_security_module; mod libs; mod mailbox; mod merkle_tree_hook; +mod metrics_fetcher; mod multisig_ism; mod payloads; mod providers; @@ -24,6 +25,7 @@ mod validator_announce; pub use self::{ aggregation_ism::*, error::*, interchain_gas::*, interchain_security_module::*, libs::*, - mailbox::*, merkle_tree_hook::*, multisig_ism::*, providers::*, routing_ism::*, signers::*, - trait_builder::*, trait_builder::*, validator_announce::*, validator_announce::*, + mailbox::*, merkle_tree_hook::*, metrics_fetcher::*, multisig_ism::*, providers::*, + routing_ism::*, signers::*, trait_builder::*, trait_builder::*, validator_announce::*, + validator_announce::*, }; diff --git a/rust/chains/hyperlane-cosmos/src/libs/address.rs b/rust/chains/hyperlane-cosmos/src/libs/address.rs index d5970b9b82f..7cfdb83caf4 100644 --- a/rust/chains/hyperlane-cosmos/src/libs/address.rs +++ b/rust/chains/hyperlane-cosmos/src/libs/address.rs @@ -12,7 +12,7 @@ use tendermint::public_key::PublicKey as TendermintPublicKey; use crate::HyperlaneCosmosError; /// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding -#[derive(new, Debug)] +#[derive(new, Debug, Clone)] pub struct CosmosAddress { /// Bech32 encoded cosmos account account_id: AccountId, diff --git a/rust/chains/hyperlane-cosmos/src/mailbox.rs b/rust/chains/hyperlane-cosmos/src/mailbox.rs index 4df968edc9b..51e8529bc33 100644 --- a/rust/chains/hyperlane-cosmos/src/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/mailbox.rs @@ -40,7 +40,7 @@ pub struct CosmosMailbox { config: ConnectionConf, domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosMailbox { @@ -51,13 +51,18 @@ impl CosmosMailbox { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { config: conf, domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } @@ -79,7 +84,7 @@ impl HyperlaneChain for CosmosMailbox { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -94,7 +99,7 @@ impl Debug for CosmosMailbox { impl Mailbox for CosmosMailbox { #[instrument(level = "debug", err, ret, skip(self))] async fn count(&self, lag: Option) -> ChainResult { - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(&self.provider.grpc(), lag).await?; self.nonce_at_block(block_height).await } @@ -107,6 +112,7 @@ impl Mailbox for CosmosMailbox { let delivered = match self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await { @@ -136,6 +142,7 @@ impl Mailbox for CosmosMailbox { let data = self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await?; let response: mailbox::DefaultIsmResponse = serde_json::from_slice(&data)?; @@ -157,6 +164,7 @@ impl Mailbox for CosmosMailbox { let data = self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await?; let response: mailbox::RecipientIsmResponse = serde_json::from_slice(&data)?; @@ -182,6 +190,7 @@ impl Mailbox for CosmosMailbox { let response: TxResponse = self .provider + .grpc() .wasm_send(process_message, tx_gas_limit) .await?; @@ -201,7 +210,11 @@ impl Mailbox for CosmosMailbox { }, }; - let gas_limit = self.provider.wasm_estimate_gas(process_message).await?; + let gas_limit = self + .provider + .grpc() + .wasm_estimate_gas(process_message) + .await?; let result = TxCostEstimate { gas_limit: gas_limit.into(), @@ -226,6 +239,7 @@ impl CosmosMailbox { let data = self .provider + .grpc() .wasm_query(GeneralMailboxQuery { mailbox: payload }, block_height) .await?; diff --git a/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs b/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs index 15db14ff7a4..f18f5a2a839 100644 --- a/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs @@ -33,7 +33,7 @@ pub struct CosmosMerkleTreeHook { /// Contract address address: H256, /// Provider - provider: Box, + provider: CosmosProvider, } impl CosmosMerkleTreeHook { @@ -43,12 +43,17 @@ impl CosmosMerkleTreeHook { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -65,7 +70,7 @@ impl HyperlaneChain for CosmosMerkleTreeHook { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -78,10 +83,11 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { tree: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(&self.provider.grpc(), lag).await?; let data = self .provider + .grpc() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -111,7 +117,7 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { count: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(&self.provider.grpc(), lag).await?; self.count_at_block(block_height).await } @@ -122,10 +128,11 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { check_point: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(&self.provider, lag).await?; + let block_height = get_block_height_for_lag(&self.provider.grpc(), lag).await?; let data = self .provider + .grpc() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -153,6 +160,7 @@ impl CosmosMerkleTreeHook { let data = self .provider + .grpc() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, diff --git a/rust/chains/hyperlane-cosmos/src/metrics_fetcher.rs b/rust/chains/hyperlane-cosmos/src/metrics_fetcher.rs new file mode 100644 index 00000000000..3cb20419c0c --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/metrics_fetcher.rs @@ -0,0 +1,52 @@ +use async_trait::async_trait; +use hyperlane_core::{ + metrics::agent::AgenMetricsFetcher, ChainResult, ContractLocator, HyperlaneChain, + HyperlaneDomain, HyperlaneProvider, U256, +}; + +use crate::{address::CosmosAddress, ConnectionConf, CosmosProvider}; + +#[derive(Debug)] +pub struct CosmosMetricsFetcher { + address: CosmosAddress, + provider: CosmosProvider, + domain: HyperlaneDomain, +} + +impl CosmosMetricsFetcher { + pub fn new( + conf: ConnectionConf, + locator: ContractLocator, + address: CosmosAddress, + ) -> ChainResult { + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + None, + )?; + + Ok(Self { + address, + provider, + domain: locator.domain.clone(), + }) + } +} + +impl HyperlaneChain for CosmosMetricsFetcher { + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +#[async_trait] +impl AgenMetricsFetcher for CosmosMetricsFetcher { + async fn get_balance(&self) -> ChainResult { + self.provider.get_balance(self.address.address()).await + } +} diff --git a/rust/chains/hyperlane-cosmos/src/multisig_ism.rs b/rust/chains/hyperlane-cosmos/src/multisig_ism.rs index a9d84dec7fb..d558acfa377 100644 --- a/rust/chains/hyperlane-cosmos/src/multisig_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/multisig_ism.rs @@ -1,9 +1,7 @@ use std::str::FromStr; use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, - payloads::ism_routes::QueryIsmGeneralRequest, - signers::Signer, + grpc::WasmProvider, payloads::ism_routes::QueryIsmGeneralRequest, signers::Signer, ConnectionConf, CosmosProvider, }; use async_trait::async_trait; @@ -19,7 +17,7 @@ use crate::payloads::multisig_ism::{self, VerifyInfoRequest, VerifyInfoRequestIn pub struct CosmosMultisigIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosMultisigIsm { @@ -29,12 +27,17 @@ impl CosmosMultisigIsm { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -51,7 +54,7 @@ impl HyperlaneChain for CosmosMultisigIsm { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -70,6 +73,7 @@ impl MultisigIsm for CosmosMultisigIsm { let data = self .provider + .grpc() .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) .await?; let response: multisig_ism::VerifyInfoResponse = serde_json::from_slice(&data)?; diff --git a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs index a47d660ded9..3a23564fc3f 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs @@ -5,6 +5,7 @@ use cosmrs::{ auth::v1beta1::{ query_client::QueryClient as QueryAccountClient, BaseAccount, QueryAccountRequest, }, + bank::v1beta1::{query_client::QueryClient as QueryBalanceClient, QueryBalanceRequest}, base::{ abci::v1beta1::TxResponse, tendermint::v1beta1::{service_client::ServiceClient, GetLatestBlockRequest}, @@ -77,14 +78,14 @@ pub trait WasmProvider: Send + Sync { async fn wasm_estimate_gas(&self, payload: T) -> ChainResult; } -#[derive(Debug)] +#[derive(Debug, Clone)] /// CosmWasm GRPC provider. pub struct WasmGrpcProvider { /// Connection configuration. conf: ConnectionConf, /// A contract address that can be used as the default /// for queries / sends / estimates. - contract_address: CosmosAddress, + contract_address: Option, /// Signer for transactions. signer: Option, /// GRPC Channel that can be cheaply cloned. @@ -96,13 +97,15 @@ impl WasmGrpcProvider { /// Create new CosmWasm GRPC Provider. pub fn new( conf: ConnectionConf, - locator: ContractLocator, + locator: Option, signer: Option, ) -> ChainResult { let endpoint = Endpoint::new(conf.get_grpc_url()).map_err(Into::::into)?; let channel = endpoint.connect_lazy(); - let contract_address = CosmosAddress::from_h256(locator.address, &conf.get_prefix())?; + let contract_address = locator + .map(|l| CosmosAddress::from_h256(l.address, &conf.get_prefix())) + .transpose()?; Ok(Self { conf, @@ -220,6 +223,35 @@ impl WasmGrpcProvider { Ok(gas_estimate) } + /// Estimates gas for a transaction containing `msgs`. + pub async fn get_balance(&self, address: String, denom: String) -> ChainResult { + let mut client = QueryBalanceClient::new(self.channel.clone()); + + let balance_request = tonic::Request::new(QueryBalanceRequest { + address, + /// denom is the coin denom to query balances for. + denom, + }); + let response = client + .balance(balance_request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + // let coin = u128::decode( + // response + // .balance + // .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? + // .amount + // ); + + let balance = response + .balance + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))?; + println!("~~~ relayer balance: {:?}", balance); + + Ok(balance.amount.parse()?) + } + /// Queries an account. async fn account_query(&self, account: String) -> ChainResult { let mut client = QueryAccountClient::new(self.channel.clone()); @@ -268,7 +300,10 @@ impl WasmProvider for WasmGrpcProvider { where T: Serialize + Send + Sync, { - self.wasm_query_to(self.contract_address.address(), payload, block_height) + let contract_address = self.contract_address.as_ref().ok_or_else(|| { + ChainCommunicationError::from_other_str("No contract address available") + })?; + self.wasm_query_to(contract_address.address(), payload, block_height) .await } @@ -308,10 +343,13 @@ impl WasmProvider for WasmGrpcProvider { { let signer = self.get_signer()?; let mut client = TxServiceClient::new(self.channel.clone()); + let contract_address = self.contract_address.as_ref().ok_or_else(|| { + ChainCommunicationError::from_other_str("No contract address available") + })?; let msgs = vec![MsgExecuteContract { sender: signer.address.clone(), - contract: self.contract_address.address(), + contract: contract_address.address(), msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), funds: vec![], } @@ -354,9 +392,12 @@ impl WasmProvider for WasmGrpcProvider { // Estimating gas requires a signer, which we can reasonably expect to have // since we need one to send a tx with the estimated gas anyways. let signer = self.get_signer()?; + let contract_address = self.contract_address.as_ref().ok_or_else(|| { + ChainCommunicationError::from_other_str("No contract address available") + })?; let msg = MsgExecuteContract { sender: signer.address.clone(), - contract: self.contract_address.address(), + contract: contract_address.address(), msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), funds: vec![], }; diff --git a/rust/chains/hyperlane-cosmos/src/providers/mod.rs b/rust/chains/hyperlane-cosmos/src/providers/mod.rs index cf9422b2f89..77296ca7563 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/mod.rs @@ -1,7 +1,13 @@ use async_trait::async_trait; use hyperlane_core::{ - BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, + BlockInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, + TxnInfo, H256, U256, }; +use tendermint_rpc::{client::CompatMode, HttpClient}; + +use crate::{ConnectionConf, HyperlaneCosmosError, Signer}; + +use self::grpc::WasmGrpcProvider; /// cosmos grpc provider pub mod grpc; @@ -9,15 +15,41 @@ pub mod grpc; pub mod rpc; /// A reference to a Cosmos chain -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CosmosProvider { domain: HyperlaneDomain, + grpc_client: WasmGrpcProvider, + _rpc_client: HttpClient, } impl CosmosProvider { /// Create a reference to a Cosmos chain - pub fn new(domain: HyperlaneDomain) -> Self { - Self { domain } + pub fn new( + domain: HyperlaneDomain, + conf: ConnectionConf, + locator: Option, + signer: Option, + ) -> ChainResult { + let grpc_client = WasmGrpcProvider::new(conf.clone(), locator, signer)?; + let _rpc_client = HttpClient::builder( + conf.get_rpc_url() + .parse() + .map_err(Into::::into)?, + ) + // Consider supporting different compatibility modes. + .compat_mode(CompatMode::latest()) + .build() + .map_err(Into::::into)?; + + Ok(Self { + domain, + _rpc_client, + grpc_client, + }) + } + + pub fn grpc(&self) -> WasmGrpcProvider { + self.grpc_client.clone() } } @@ -27,9 +59,7 @@ impl HyperlaneChain for CosmosProvider { } fn provider(&self) -> Box { - Box::new(CosmosProvider { - domain: self.domain.clone(), - }) + Box::new(self.clone()) } } @@ -47,4 +77,13 @@ impl HyperlaneProvider for CosmosProvider { // FIXME Ok(true) } + + async fn get_balance(&self, address: String) -> ChainResult { + // denom is of the form "untrn" + Ok(self + .grpc_client + .get_balance(address, "".to_string()) + .await? + .into()) + } } diff --git a/rust/chains/hyperlane-cosmos/src/routing_ism.rs b/rust/chains/hyperlane-cosmos/src/routing_ism.rs index 0a646c005be..97360546f42 100644 --- a/rust/chains/hyperlane-cosmos/src/routing_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/routing_ism.rs @@ -22,7 +22,7 @@ use crate::{ pub struct CosmosRoutingIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosRoutingIsm { @@ -32,12 +32,17 @@ impl CosmosRoutingIsm { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -54,7 +59,7 @@ impl HyperlaneChain for CosmosRoutingIsm { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -69,6 +74,7 @@ impl RoutingIsm for CosmosRoutingIsm { let data = self .provider + .grpc() .wasm_query( QueryRoutingIsmGeneralRequest { routing_ism: payload, diff --git a/rust/chains/hyperlane-cosmos/src/validator_announce.rs b/rust/chains/hyperlane-cosmos/src/validator_announce.rs index 69f7121b888..6b0ee04930e 100644 --- a/rust/chains/hyperlane-cosmos/src/validator_announce.rs +++ b/rust/chains/hyperlane-cosmos/src/validator_announce.rs @@ -7,7 +7,7 @@ use hyperlane_core::{ }; use crate::{ - grpc::{WasmGrpcProvider, WasmProvider}, + grpc::WasmProvider, payloads::validator_announce::{ self, AnnouncementRequest, AnnouncementRequestInner, GetAnnounceStorageLocationsRequest, GetAnnounceStorageLocationsRequestInner, @@ -22,7 +22,7 @@ use crate::{ pub struct CosmosValidatorAnnounce { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: CosmosProvider, } impl CosmosValidatorAnnounce { @@ -32,12 +32,17 @@ impl CosmosValidatorAnnounce { locator: ContractLocator, signer: Option, ) -> ChainResult { - let provider = WasmGrpcProvider::new(conf.clone(), locator.clone(), signer)?; + let provider = CosmosProvider::new( + locator.domain.clone(), + conf.clone(), + Some(locator.clone()), + signer, + )?; Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } } @@ -54,7 +59,7 @@ impl HyperlaneChain for CosmosValidatorAnnounce { } fn provider(&self) -> Box { - Box::new(CosmosProvider::new(self.domain.clone())) + Box::new(self.provider.clone()) } } @@ -76,7 +81,7 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { }, }; - let data: Vec = self.provider.wasm_query(payload, None).await?; + let data: Vec = self.provider.grpc().wasm_query(payload, None).await?; let response: validator_announce::GetAnnounceStorageLocationsResponse = serde_json::from_slice(&data)?; @@ -102,6 +107,7 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { let response: TxResponse = self .provider + .grpc() .wasm_send(announce_request, tx_gas_limit) .await?; diff --git a/rust/chains/hyperlane-ethereum/src/agent_metrics.rs b/rust/chains/hyperlane-ethereum/src/agent_metrics.rs new file mode 100644 index 00000000000..3b96ed27baa --- /dev/null +++ b/rust/chains/hyperlane-ethereum/src/agent_metrics.rs @@ -0,0 +1,12 @@ +use async_trait::async_trait; +use hyperlane_core::{metrics::agent::AgenMetricsFetcher, ChainResult, U256}; + +pub struct EthereumMetricsFetcher {} + +#[async_trait] +impl AgenMetricsFetcher for EthereumMetricsFetcher { + async fn get_balance(&self, address: String) -> ChainResult { + // Should the provider be part of the `MetricsFetcher`? + Ok(0.into()) + } +} diff --git a/rust/chains/hyperlane-ethereum/src/lib.rs b/rust/chains/hyperlane-ethereum/src/lib.rs index 2d42850bc41..27d75b3d575 100644 --- a/rust/chains/hyperlane-ethereum/src/lib.rs +++ b/rust/chains/hyperlane-ethereum/src/lib.rs @@ -74,6 +74,7 @@ mod signers; #[cfg(not(doctest))] mod singleton_signer; +pub mod agent_metrics; mod config; fn extract_fn_map(abi: &'static Lazy) -> HashMap, &'static str> { diff --git a/rust/chains/hyperlane-ethereum/src/provider.rs b/rust/chains/hyperlane-ethereum/src/provider.rs index 6ea06433d6f..871fc2a4644 100644 --- a/rust/chains/hyperlane-ethereum/src/provider.rs +++ b/rust/chains/hyperlane-ethereum/src/provider.rs @@ -6,7 +6,7 @@ use std::time::Duration; use async_trait::async_trait; use derive_new::new; use ethers::prelude::Middleware; -use hyperlane_core::ethers_core_types; +use hyperlane_core::{ethers_core_types, U256}; use tokio::time::sleep; use tracing::instrument; @@ -105,6 +105,10 @@ where .map_err(ChainCommunicationError::from_other)?; Ok(!code.is_empty()) } + + async fn get_balance(&self, address: String) -> ChainResult { + todo!() + } } impl EthereumProvider diff --git a/rust/chains/hyperlane-fuel/src/provider.rs b/rust/chains/hyperlane-fuel/src/provider.rs index 92303f57950..8048076e041 100644 --- a/rust/chains/hyperlane-fuel/src/provider.rs +++ b/rust/chains/hyperlane-fuel/src/provider.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use hyperlane_core::{ - BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, + BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, U256, }; /// A wrapper around a fuel provider to get generic blockchain information. @@ -31,4 +31,8 @@ impl HyperlaneProvider for FuelProvider { async fn is_contract(&self, address: &H256) -> ChainResult { todo!() } + + async fn get_balance(&self, address: String) -> ChainResult { + todo!() + } } diff --git a/rust/chains/hyperlane-sealevel/src/provider.rs b/rust/chains/hyperlane-sealevel/src/provider.rs index b853e30e4b8..3f7449aef2b 100644 --- a/rust/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/chains/hyperlane-sealevel/src/provider.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use hyperlane_core::{ - BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, + BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, U256, }; /// A wrapper around a Sealevel provider to get generic blockchain information. @@ -43,4 +43,8 @@ impl HyperlaneProvider for SealevelProvider { // FIXME Ok(true) } + + async fn get_balance(&self, _address: String) -> ChainResult { + todo!() + } } diff --git a/rust/ethers-prometheus/src/middleware/mod.rs b/rust/ethers-prometheus/src/middleware/mod.rs index 18db31d764b..19129af6123 100644 --- a/rust/ethers-prometheus/src/middleware/mod.rs +++ b/rust/ethers-prometheus/src/middleware/mod.rs @@ -14,7 +14,7 @@ use ethers::abi::AbiEncode; use ethers::prelude::*; use ethers::types::transaction::eip2718::TypedTransaction; use ethers::utils::hex::ToHex; -use log::{debug, trace, warn}; +use log::{debug, trace}; use maplit::hashmap; use prometheus::{CounterVec, GaugeVec, IntCounterVec, IntGaugeVec}; use static_assertions::assert_impl_all; @@ -23,35 +23,11 @@ use tokio::time::MissedTickBehavior; pub use error::PrometheusMiddlewareError; -use crate::contracts::erc_20::Erc20; use crate::u256_as_scaled_f64; pub use crate::ChainInfo; mod error; -/// Some basic information about a token. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))] -pub struct TokenInfo { - /// Full name of the token. E.g. Ether. - pub name: String, - /// Token symbol. E.g. ETH. - pub symbol: String, - /// Number of - pub decimals: u8, -} - -impl Default for TokenInfo { - fn default() -> Self { - Self { - name: "Unknown".into(), - symbol: "".into(), - decimals: 18, - } - } -} - /// Some basic information about a wallet. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] @@ -148,18 +124,6 @@ pub const TRANSACTION_SEND_TOTAL_LABELS: &[&str] = /// Help string for the metric. pub const TRANSACTION_SEND_TOTAL_HELP: &str = "Number of transactions sent"; -/// Expected label names for the `wallet_balance` metric. -pub const WALLET_BALANCE_LABELS: &[&str] = &[ - "chain", - "wallet_address", - "wallet_name", - "token_address", - "token_symbol", - "token_name", -]; -/// Help string for the metric. -pub const WALLET_BALANCE_HELP: &str = "Current balance of eth and other tokens in the `tokens` map for the wallet addresses in the `wallets` set"; - /// Container for all the relevant middleware metrics. #[derive(Clone, Builder)] pub struct MiddlewareMetrics { @@ -238,24 +202,23 @@ pub struct MiddlewareMetrics { /// - `txn_status`: `dispatched`, `completed`, or `failed` #[builder(setter(into, strip_option), default)] transaction_send_total: Option, - // /// Gas spent on completed transactions. // /// - `chain`: the chain name (or ID if the name is unknown) of the chain the tx occurred // on. /// - `address_from`: source address of the transaction. // /// - `address_to`: destination address of the transaction. // #[builder(setter(into, strip_option), default)] // transaction_send_gas_eth_total: Option, - /// Current balance of eth and other tokens in the `tokens` map for the - /// wallet addresses in the `wallets` set. - /// - `chain`: the chain name (or chain ID if the name is unknown) of the - /// chain the tx occurred on. - /// - `wallet_address`: Address of the wallet holding the funds. - /// - `wallet_name`: Name of the address holding the funds. - /// - `token_address`: Address of the token. - /// - `token_symbol`: Symbol of the token. - /// - `token_name`: Full name of the token. - #[builder(setter(into, strip_option), default)] - wallet_balance: Option, + // /// Current balance of eth and other tokens in the `tokens` map for the + // /// wallet addresses in the `wallets` set. + // /// - `chain`: the chain name (or chain ID if the name is unknown) of the + // /// chain the tx occurred on. + // /// - `wallet_address`: Address of the wallet holding the funds. + // /// - `wallet_name`: Name of the address holding the funds. + // /// - `token_address`: Address of the token. + // /// - `token_symbol`: Symbol of the token. + // /// - `token_name`: Full name of the token. + // #[builder(setter(into, strip_option), default)] + // wallet_balance: Option, } /// An ethers-rs middleware that instruments calls with prometheus metrics. To @@ -273,14 +236,9 @@ pub struct PrometheusMiddleware { #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))] pub struct PrometheusMiddlewareConf { - /// The tokens to track and identifying info - #[cfg_attr(feature = "serde", serde(default))] - pub tokens: HashMap, - - /// The wallets to track and identifying info - #[cfg_attr(feature = "serde", serde(default))] - pub wallets: HashMap, - + // /// The wallets to track and identifying info + // #[cfg_attr(feature = "serde", serde(default))] + // pub wallets: HashMap, /// Contract info for more useful metrics #[cfg_attr(feature = "serde", serde(default))] pub contracts: HashMap, @@ -521,32 +479,6 @@ impl PrometheusMiddleware { conf: Arc::new(RwLock::new(conf)), } } - - /// Start tracking metrics for a new token. - pub async fn track_new_token(&self, addr: Address, info: TokenInfo) { - self.track_new_tokens([(addr, info)]).await; - } - - /// Start tacking metrics for new tokens. - pub async fn track_new_tokens(&self, iter: impl IntoIterator) { - let mut data = self.conf.write().await; - for (addr, info) in iter { - data.tokens.insert(addr, info); - } - } - - /// Start tracking metrics for a new wallet. - pub async fn track_new_wallet(&self, addr: Address, info: WalletInfo) { - self.track_new_wallets([(addr, info)]).await; - } - - /// Start tracking metrics for new wallets. - pub async fn track_new_wallets(&self, iter: impl IntoIterator) { - let mut data = self.conf.write().await; - for (addr, info) in iter { - data.wallets.insert(addr, info); - } - } } impl PrometheusMiddleware { @@ -580,7 +512,7 @@ impl PrometheusMiddleware { /// prometheus scrape interval. pub fn update(&self) -> impl Future { // all metrics are Arcs internally so just clone the ones we want to report for. - let wallet_balance = self.metrics.wallet_balance.clone(); + // let wallet_balance = self.metrics.wallet_balance.clone(); let block_height = self.metrics.block_height.clone(); let gas_price_gwei = self.metrics.gas_price_gwei.clone(); @@ -595,9 +527,9 @@ impl PrometheusMiddleware { if block_height.is_some() || gas_price_gwei.is_some() { Self::update_block_details(&*client, chain, block_height, gas_price_gwei).await; } - if let Some(wallet_balance) = wallet_balance { - Self::update_wallet_balances(client.clone(), &data, chain, wallet_balance).await; - } + // if let Some(wallet_balance) = wallet_balance { + // Self::update_wallet_balances(client.clone(), &data, chain, wallet_balance).await; + // } // more metrics to come... } @@ -609,9 +541,7 @@ impl PrometheusMiddleware { block_height: Option, gas_price_gwei: Option, ) { - let current_block = if let Ok(Some(b)) = client.get_block(BlockNumber::Latest).await { - b - } else { + let Ok(Some(current_block)) = client.get_block(BlockNumber::Latest).await else { return; }; @@ -635,63 +565,6 @@ impl PrometheusMiddleware { } } } - - async fn update_wallet_balances( - client: Arc, - data: &PrometheusMiddlewareConf, - chain: &str, - wallet_balance_metric: GaugeVec, - ) { - for (wallet_addr, wallet_info) in data.wallets.iter() { - let wallet_addr_str: String = wallet_addr.encode_hex(); - let wallet_name = wallet_info.name.as_deref().unwrap_or("none"); - - match client.get_balance(*wallet_addr, None).await { - Ok(balance) => { - // Okay, so the native type is not a token, but whatever, close enough. - // Note: This is ETH for many chains, but not all so that is why we use `N` and `Native` - // TODO: can we get away with scaling as 18 in all cases here? I am guessing not. - let balance = u256_as_scaled_f64(balance, 18); - trace!("Wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance is {balance} of the native currency"); - wallet_balance_metric - .with(&hashmap! { - "chain" => chain, - "wallet_address" => wallet_addr_str.as_str(), - "wallet_name" => wallet_name, - "token_address" => "none", - "token_symbol" => "Native", - "token_name" => "Native" - }).set(balance) - }, - Err(e) => warn!("Metric update failed for wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance for native currency; {e}") - } - for (token_addr, token) in data.tokens.iter() { - let token_addr_str: String = token_addr.encode_hex(); - let balance = match Erc20::new(*token_addr, client.clone()) - .balance_of(*wallet_addr) - .call() - .await - { - Ok(b) => u256_as_scaled_f64(b, token.decimals), - Err(e) => { - warn!("Metric update failed for wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance for {name}; {e}", name=token.name); - continue; - } - }; - trace!("Wallet {wallet_name} ({wallet_addr_str}) on chain {chain} balance is {balance}{}", token.symbol); - wallet_balance_metric - .with(&hashmap! { - "chain" => chain, - "wallet_address" => wallet_addr_str.as_str(), - "wallet_name" => wallet_name, - "token_address" => token_addr_str.as_str(), - "token_symbol" => token.symbol.as_str(), - "token_name" => token.symbol.as_str() - }) - .set(balance); - } - } - } } impl Debug for PrometheusMiddleware { diff --git a/rust/hyperlane-base/Cargo.toml b/rust/hyperlane-base/Cargo.toml index 02d870e6440..4e78c302431 100644 --- a/rust/hyperlane-base/Cargo.toml +++ b/rust/hyperlane-base/Cargo.toml @@ -15,6 +15,7 @@ bs58.workspace = true color-eyre = { workspace = true, optional = true } config.workspace = true convert_case.workspace = true +derive_builder.workspace = true derive-new.workspace = true ed25519-dalek.workspace = true ethers.workspace = true @@ -22,6 +23,7 @@ eyre.workspace = true fuels.workspace = true futures-util.workspace = true itertools.workspace = true +maplit.workspace = true paste.workspace = true prometheus.workspace = true rocksdb.workspace = true diff --git a/rust/hyperlane-base/src/metrics/agent.rs b/rust/hyperlane-base/src/metrics/agent.rs new file mode 100644 index 00000000000..06339915905 --- /dev/null +++ b/rust/hyperlane-base/src/metrics/agent.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; + +use async_trait::async_trait; +use derive_builder::Builder; +use derive_new::new; +use eyre::Result; +use hyperlane_core::{ + metrics::agent::AgenMetricsFetcher, ChainResult, HyperlaneDomain, H256, U256, +}; +use maplit::hashmap; +use prometheus::GaugeVec; +use tracing::{trace, warn}; + +use crate::CoreMetrics; + +/// Expected label names for the `wallet_balance` metric. +pub const WALLET_BALANCE_LABELS: &[&str] = &[ + "chain", + "wallet_address", + "wallet_name", + "token_address", + "token_symbol", + "token_name", +]; +/// Help string for the metric. +pub const WALLET_BALANCE_HELP: &str = + "Current native token balance for the wallet addresses in the `wallets` set"; + +#[derive(Clone, Builder)] +pub struct AgentMetrics { + /// Current balance of eth and other tokens in the `tokens` map for the + /// wallet addresses in the `wallets` set. + /// - `chain`: the chain name (or chain ID if the name is unknown) of the + /// chain the tx occurred on. + /// - `wallet_address`: Address of the wallet holding the funds. + /// - `wallet_name`: Name of the address holding the funds. + /// - `token_address`: Address of the token. + /// - `token_symbol`: Symbol of the token. + /// - `token_name`: Full name of the token. + #[builder(setter(into, strip_option), default)] + wallet_balance: Option, +} + +pub(crate) fn create_agent_metrics(metrics: &CoreMetrics) -> Result { + Ok(AgentMetricsBuilder::default() + .wallet_balance(metrics.new_gauge( + "wallet_balance", + WALLET_BALANCE_HELP, + WALLET_BALANCE_LABELS, + )?) + .build()?) +} + +/// Configuration for the prometheus middleware. This can be loaded via serde. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))] +pub struct AgentMetricsConf { + /// The account to track + #[cfg_attr(feature = "serde", serde(default))] + pub address: Option, + + /// Information about the chain this metric is for + pub domain: HyperlaneDomain, + + pub name: String, +} + +#[derive(new)] +pub struct PrometheusAgent { + metrics: AgentMetrics, + conf: AgentMetricsConf, + fetcher: Box, +} + +impl PrometheusAgent { + async fn update_wallet_balances(&self) { + let Some(wallet_addr) = self.conf.address.clone() else { + return; + }; + let wallet_name = self.conf.name.clone(); + let Some(wallet_balance_metric) = self.metrics.wallet_balance.clone() else { + return; + }; + let chain = self.conf.domain.name(); + + match self.fetcher.get_balance(&wallet_addr).await { + Ok(balance) => { + // Okay, so the native type is not a token, but whatever, close enough. + // Note: This is ETH for many chains, but not all so that is why we use `N` and `Native` + // TODO: can we get away with scaling as 18 in all cases here? I am guessing not. + let balance = u256_as_scaled_f64(U256::from(balance), 18); + trace!("Wallet {wallet_name} ({wallet_addr}) on chain {chain} balance is {balance} of the native currency"); + wallet_balance_metric + .with(&hashmap! { + "chain" => chain, + "wallet_address" => wallet_addr.as_str(), + "wallet_name" => wallet_name.as_str(), + "token_address" => "none", + "token_symbol" => "Native", + "token_name" => "Native" + }).set(balance) + }, + Err(e) => warn!("Metric update failed for wallet {wallet_name} ({wallet_addr}) on chain {chain} balance for native currency; {e}") + } + } +} + +/// Convert a u256 scaled integer value into the corresponding f64 value. +fn u256_as_scaled_f64(value: U256, decimals: u8) -> f64 { + value.to_f64_lossy() / (10u64.pow(decimals as u32) as f64) +} diff --git a/rust/hyperlane-base/src/metrics/mod.rs b/rust/hyperlane-base/src/metrics/mod.rs index ff30be6dc78..283b5474d90 100644 --- a/rust/hyperlane-base/src/metrics/mod.rs +++ b/rust/hyperlane-base/src/metrics/mod.rs @@ -6,5 +6,6 @@ pub const NAMESPACE: &str = "hyperlane"; mod core; pub use self::core::*; +pub(crate) mod agent; mod json_rpc_client; mod provider; diff --git a/rust/hyperlane-base/src/metrics/provider.rs b/rust/hyperlane-base/src/metrics/provider.rs index 86a9fd5602e..54def51ae0a 100644 --- a/rust/hyperlane-base/src/metrics/provider.rs +++ b/rust/hyperlane-base/src/metrics/provider.rs @@ -46,10 +46,5 @@ pub(crate) fn create_provider_metrics(metrics: &CoreMetrics) -> Result Result> { + let ctx = "Building Agent Metrics Fetcher"; + + match &self.connection { + ChainConnectionConf::Ethereum(conf) => { + Ok(Box::new(h_eth::agent_metrics::EthereumMetricsFetcher {})) + } + ChainConnectionConf::Fuel(_) => todo!(), + ChainConnectionConf::Sealevel(_) => todo!(), + ChainConnectionConf::Cosmos(conf) => CosmosProvider::new(domain, conf, locator), + } + // .context(ctx) + } + async fn signer(&self) -> Result> { if let Some(conf) = &self.signer { Ok(Some(conf.build::().await?)) @@ -639,13 +655,18 @@ impl ChainConf { self.signer().await } + pub async fn agent_metrics_conf(&self, agent_name: String) -> Result { + let chain_signer_address = self.chain_signer().await?.map(|s| s.address_string()); + Ok(AgentMetricsConf { + address: chain_signer_address, + domain: self.domain.clone(), + name: agent_name, + }) + } + /// Get a clone of the ethereum metrics conf with correctly configured /// contract information. - fn metrics_conf( - &self, - agent_name: &str, - signer: &Option, - ) -> PrometheusMiddlewareConf { + pub fn metrics_conf(&self, agent_name: &str) -> PrometheusMiddlewareConf { let mut cfg = self.metrics_conf.clone(); if cfg.chain.is_none() { @@ -654,14 +675,6 @@ impl ChainConf { }); } - if let Some(signer) = signer { - cfg.wallets - .entry(signer.eth_address().into()) - .or_insert_with(|| WalletInfo { - name: Some(agent_name.into()), - }); - } - let mut register_contract = |name: &str, address: H256, fns: HashMap, String>| { cfg.contracts .entry(address.into()) @@ -718,7 +731,7 @@ impl ChainConf { B: BuildableWithProvider + Sync, { let signer = self.ethereum_signer().await?; - let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer); + let metrics_conf = self.metrics_conf(metrics.agent_name()); let rpc_metrics = Some(metrics.json_rpc_client_metrics()); let middleware_metrics = Some((metrics.provider_metrics(), metrics_conf)); let res = builder diff --git a/rust/hyperlane-core/src/chain.rs b/rust/hyperlane-core/src/chain.rs index 94c2c5c44e7..01d5ec7c3d2 100644 --- a/rust/hyperlane-core/src/chain.rs +++ b/rust/hyperlane-core/src/chain.rs @@ -5,6 +5,7 @@ use std::{ hash::{Hash, Hasher}, }; +use derive_new::new; use num_derive::FromPrimitive; use num_traits::FromPrimitive; #[cfg(feature = "strum")] @@ -18,7 +19,7 @@ pub struct Address(pub bytes::Bytes); #[derive(Debug, Clone)] pub struct Balance(pub num::BigInt); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, new)] pub struct ContractLocator<'a> { pub domain: &'a HyperlaneDomain, pub address: H256, diff --git a/rust/hyperlane-core/src/lib.rs b/rust/hyperlane-core/src/lib.rs index 0e8349186bc..3d91f35fccd 100644 --- a/rust/hyperlane-core/src/lib.rs +++ b/rust/hyperlane-core/src/lib.rs @@ -26,6 +26,7 @@ pub mod utils; pub mod test_utils; pub mod config; +pub mod metrics; /// Core hyperlane system data structures mod types; diff --git a/rust/hyperlane-core/src/metrics/agent.rs b/rust/hyperlane-core/src/metrics/agent.rs new file mode 100644 index 00000000000..d124be66d27 --- /dev/null +++ b/rust/hyperlane-core/src/metrics/agent.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; + +use async_trait::async_trait; +use derive_new::new; +use eyre::Result; + +use crate::{ChainResult, U256}; + +#[async_trait] +pub trait AgenMetricsFetcher { + async fn get_balance(&self) -> ChainResult; +} + +// /// Convert a u256 scaled integer value into the corresponding f64 value. +// fn u256_as_scaled_f64(value: U256, decimals: u8) -> f64 { +// value.to_f64_lossy() / (10u64.pow(decimals as u32) as f64) +// } diff --git a/rust/hyperlane-core/src/metrics/mod.rs b/rust/hyperlane-core/src/metrics/mod.rs new file mode 100644 index 00000000000..f17bc55db89 --- /dev/null +++ b/rust/hyperlane-core/src/metrics/mod.rs @@ -0,0 +1 @@ +pub mod agent; diff --git a/rust/hyperlane-core/src/traits/provider.rs b/rust/hyperlane-core/src/traits/provider.rs index 3f00e7a4ac0..ec8cae7052d 100644 --- a/rust/hyperlane-core/src/traits/provider.rs +++ b/rust/hyperlane-core/src/traits/provider.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use auto_impl::auto_impl; use thiserror::Error; -use crate::{BlockInfo, ChainResult, HyperlaneChain, TxnInfo, H256}; +use crate::{BlockInfo, ChainResult, HyperlaneChain, TxnInfo, H256, U256}; /// Interface for a provider. Allows abstraction over different provider types /// for different chains. @@ -24,6 +24,9 @@ pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { /// Returns whether a contract exists at the provided address async fn is_contract(&self, address: &H256) -> ChainResult; + + /// Returns the native currency balance of the given address + async fn get_balance(&self, address: String) -> ChainResult; } /// Errors when querying for provider information. diff --git a/rust/utils/run-locally/src/cosmos/mod.rs b/rust/utils/run-locally/src/cosmos/mod.rs index 1ecb26dc0cc..3b3a9078497 100644 --- a/rust/utils/run-locally/src/cosmos/mod.rs +++ b/rust/utils/run-locally/src/cosmos/mod.rs @@ -537,7 +537,7 @@ fn run_locally() { } fn termination_invariants_met(_messages_expected: u32) -> eyre::Result { - Ok(true) + Ok(false) // TODO: uncomment once CI passes consistently on Ubuntu // let gas_payments_scraped = fetch_metric( // "9093",