Relayer balance metrics (#2976)
Done: - scaffolding for fetching custom agent metrics - abstractions for building a metrics fetcher for a given VM - querying cosmos balances; e2e tested. - querying evm balances; e2e tested. - **Note that as a result, evm addresses are now no longer zero-padded when printed in the logs. This may break existing log queries** - fixed a nasty bug on ubuntu where wasmd (osmosisd dependency, part of the grpc query flow) would panic when a block is specified via `x-cosmos-block-height`. The fix was to bump the version of osmosisd from `19.0.0` to `20.5.0`. **Note that when running e2e on Mac OS, the osmosis version in use is still 19.0.0**. That's because we need a fork that publishes a darwin target binary (currently pointing [here](https://github.com/hashableric/osmosis/releases/download/v19.0.0-mnts/osmosisd-19.0.0-mnts-darwin-arm64.tar.gz)) For follow up PR: - sealevel balance querying I'm open to all renaming suggestions, I just tried to speed through and didn't ponder names too much Relates to https://github.com/hyperlane-xyz/issues/issues/701 Closes https://github.com/hyperlane-xyz/issues/issues/702 (because the balance becomes available in the metrics endpoint for polling)pull/3025/head
parent
2da6ccebe8
commit
77aa58c581
@ -0,0 +1,120 @@ |
||||
use std::time::Duration; |
||||
|
||||
use derive_builder::Builder; |
||||
use derive_new::new; |
||||
use eyre::Result; |
||||
use hyperlane_core::metrics::agent::u256_as_scaled_f64; |
||||
use hyperlane_core::HyperlaneDomain; |
||||
use hyperlane_core::HyperlaneProvider; |
||||
use maplit::hashmap; |
||||
use prometheus::GaugeVec; |
||||
use tokio::time::MissedTickBehavior; |
||||
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"; |
||||
|
||||
/// Agent-specific metrics
|
||||
#[derive(Clone, Builder)] |
||||
pub struct AgentMetrics { |
||||
/// Current balance of native tokens for the
|
||||
/// wallet address.
|
||||
/// - `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<GaugeVec>, |
||||
} |
||||
|
||||
pub(crate) fn create_agent_metrics(metrics: &CoreMetrics) -> Result<AgentMetrics> { |
||||
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<String>, |
||||
|
||||
/// Information about the chain this metric is for
|
||||
pub domain: HyperlaneDomain, |
||||
|
||||
/// Name of the agent the metrics are about
|
||||
pub name: String, |
||||
} |
||||
|
||||
/// Utility struct to update agent metrics for a given chain
|
||||
#[derive(new)] |
||||
pub struct AgentMetricsUpdater { |
||||
metrics: AgentMetrics, |
||||
conf: AgentMetricsConf, |
||||
provider: Box<dyn HyperlaneProvider>, |
||||
} |
||||
|
||||
impl AgentMetricsUpdater { |
||||
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.provider.get_balance(wallet_addr.clone()).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, self.conf.domain.domain_protocol()); |
||||
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}") |
||||
} |
||||
} |
||||
|
||||
/// Periodically updates the metrics
|
||||
pub async fn start_updating_on_interval(self, period: Duration) { |
||||
let mut interval = tokio::time::interval(period); |
||||
interval.set_missed_tick_behavior(MissedTickBehavior::Skip); |
||||
loop { |
||||
self.update_wallet_balances().await; |
||||
interval.tick().await; |
||||
} |
||||
} |
||||
} |
@ -1,10 +1,14 @@ |
||||
//! Useful metrics that all agents should track.
|
||||
|
||||
pub use self::core::*; |
||||
|
||||
/// The metrics namespace prefix. All metric names will start with `{NAMESPACE}_`.
|
||||
pub const NAMESPACE: &str = "hyperlane"; |
||||
|
||||
mod core; |
||||
pub use self::core::*; |
||||
|
||||
mod agent_metrics; |
||||
mod json_rpc_client; |
||||
mod provider; |
||||
|
||||
pub use self::agent_metrics::*; |
||||
|
@ -0,0 +1,29 @@ |
||||
use crate::HyperlaneDomainProtocol; |
||||
use std::time::Duration; |
||||
|
||||
use crate::U256; |
||||
|
||||
const ETHEREUM_DECIMALS: u8 = 18; |
||||
const COSMOS_DECIMALS: u8 = 6; |
||||
const SOLANA_DECIMALS: u8 = 9; |
||||
|
||||
/// Interval for querying the prometheus metrics endpoint.
|
||||
/// This should be whatever the prometheus scrape interval is
|
||||
pub const METRICS_SCRAPE_INTERVAL: Duration = Duration::from_secs(60); |
||||
|
||||
/// Convert a u256 scaled integer value into the corresponding f64 value.
|
||||
#[cfg(feature = "float")] |
||||
pub fn u256_as_scaled_f64(value: U256, domain: HyperlaneDomainProtocol) -> f64 { |
||||
let decimals = decimals_by_protocol(domain); |
||||
value.to_f64_lossy() / (10u64.pow(decimals as u32) as f64) |
||||
} |
||||
|
||||
/// Get the decimals each protocol typically uses for its lowest denomination
|
||||
/// of the native token
|
||||
pub fn decimals_by_protocol(protocol: HyperlaneDomainProtocol) -> u8 { |
||||
match protocol { |
||||
HyperlaneDomainProtocol::Cosmos => COSMOS_DECIMALS, |
||||
HyperlaneDomainProtocol::Sealevel => SOLANA_DECIMALS, |
||||
_ => ETHEREUM_DECIMALS, |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
/// Agent metrics utils
|
||||
pub mod agent; |
Loading…
Reference in new issue