From 98bb228a96dd84ce884c8aa84097f859f5df6fa4 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:54:04 +0000 Subject: [PATCH] Sealevel balance metrics (#3025) Publishes sealevel balance as a relayer metric **Drive-by Changes** - Creates `HyperlaneSealevelError`, a `hyperlane-sealevel` specific type to avoid including sealevel errors in `hyperlane-core`'s `ChainCommunicationError`. It's pretty empty now, but we have a starting point now. This follows the approach used in `hyperlane-cosmos` too. - Includes an rpc provider instance in `SealevelProvider`, and uses it to add `get_balance` on `SealevelProvider`. **Testing** None, as sealevel e2e is currently disabled --- rust/chains/hyperlane-sealevel/src/error.rs | 23 +++++++++ .../hyperlane-sealevel/src/interchain_gas.rs | 11 ++--- .../src/interchain_security_module.rs | 20 ++++---- rust/chains/hyperlane-sealevel/src/lib.rs | 1 + rust/chains/hyperlane-sealevel/src/mailbox.rs | 48 +++++++++---------- .../src/merkle_tree_hook.rs | 6 +-- .../hyperlane-sealevel/src/multisig_ism.rs | 16 ++++--- .../chains/hyperlane-sealevel/src/provider.rs | 37 ++++++++++++-- .../src/validator_announce.rs | 16 ++++--- 9 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 rust/chains/hyperlane-sealevel/src/error.rs diff --git a/rust/chains/hyperlane-sealevel/src/error.rs b/rust/chains/hyperlane-sealevel/src/error.rs new file mode 100644 index 0000000000..55b81b4167 --- /dev/null +++ b/rust/chains/hyperlane-sealevel/src/error.rs @@ -0,0 +1,23 @@ +use hyperlane_core::ChainCommunicationError; +use solana_client::client_error::ClientError; +use solana_sdk::pubkey::ParsePubkeyError; + +/// Errors from the crates specific to the hyperlane-sealevel +/// implementation. +/// This error can then be converted into the broader error type +/// in hyperlane-core using the `From` trait impl +#[derive(Debug, thiserror::Error)] +pub enum HyperlaneSealevelError { + /// ParsePubkeyError error + #[error("{0}")] + ParsePubkeyError(#[from] ParsePubkeyError), + /// ClientError error + #[error("{0}")] + ClientError(#[from] ClientError), +} + +impl From for ChainCommunicationError { + fn from(value: HyperlaneSealevelError) -> Self { + ChainCommunicationError::from_other(value) + } +} diff --git a/rust/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/chains/hyperlane-sealevel/src/interchain_gas.rs index 3c3adedd25..411505aae2 100644 --- a/rust/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -35,6 +35,7 @@ pub struct SealevelInterchainGasPaymaster { data_pda_pubkey: Pubkey, domain: HyperlaneDomain, igp_account: H256, + provider: SealevelProvider, } impl SealevelInterchainGasPaymaster { @@ -43,12 +44,9 @@ impl SealevelInterchainGasPaymaster { conf: &ConnectionConf, igp_account_locator: &ContractLocator<'_>, ) -> ChainResult { - let rpc_client = RpcClientWithDebug::new_with_commitment( - conf.url.to_string(), - CommitmentConfig::processed(), - ); + let provider = SealevelProvider::new(igp_account_locator.domain.clone(), conf); let program_id = - Self::determine_igp_program_id(&rpc_client, &igp_account_locator.address).await?; + Self::determine_igp_program_id(provider.rpc(), &igp_account_locator.address).await?; let (data_pda_pubkey, _) = Pubkey::find_program_address(igp_program_data_pda_seeds!(), &program_id); @@ -57,6 +55,7 @@ impl SealevelInterchainGasPaymaster { data_pda_pubkey, domain: igp_account_locator.domain.clone(), igp_account: igp_account_locator.address, + provider, }) } @@ -91,7 +90,7 @@ impl HyperlaneChain for SealevelInterchainGasPaymaster { } fn provider(&self) -> Box { - Box::new(SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } diff --git a/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs b/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs index 953b2eac5b..0f92432eb1 100644 --- a/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-sealevel/src/interchain_security_module.rs @@ -10,29 +10,31 @@ use hyperlane_core::{ use hyperlane_sealevel_interchain_security_module_interface::InterchainSecurityModuleInstruction; use serializable_account_meta::SimulationReturnData; -use crate::{utils::simulate_instruction, ConnectionConf, RpcClientWithDebug}; +use crate::{utils::simulate_instruction, ConnectionConf, RpcClientWithDebug, SealevelProvider}; /// A reference to an InterchainSecurityModule contract on some Sealevel chain #[derive(Debug)] pub struct SealevelInterchainSecurityModule { - rpc_client: RpcClientWithDebug, payer: Option, program_id: Pubkey, - domain: HyperlaneDomain, + provider: SealevelProvider, } impl SealevelInterchainSecurityModule { /// Create a new sealevel InterchainSecurityModule pub fn new(conf: &ConnectionConf, locator: ContractLocator, payer: Option) -> Self { - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); Self { - rpc_client, payer, program_id, - domain: locator.domain.clone(), + provider, } } + + fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } } impl HyperlaneContract for SealevelInterchainSecurityModule { @@ -43,11 +45,11 @@ impl HyperlaneContract for SealevelInterchainSecurityModule { impl HyperlaneChain for SealevelInterchainSecurityModule { fn domain(&self) -> &HyperlaneDomain { - &self.domain + self.provider.domain() } fn provider(&self) -> Box { - Box::new(crate::SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -63,7 +65,7 @@ impl InterchainSecurityModule for SealevelInterchainSecurityModule { ); let module = simulate_instruction::>( - &self.rpc_client, + self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, diff --git a/rust/chains/hyperlane-sealevel/src/lib.rs b/rust/chains/hyperlane-sealevel/src/lib.rs index 864e449061..8cd8830f57 100644 --- a/rust/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/chains/hyperlane-sealevel/src/lib.rs @@ -15,6 +15,7 @@ pub use solana_sdk::signer::keypair::Keypair; pub use trait_builder::*; pub use validator_announce::*; +mod error; mod interchain_gas; mod interchain_security_module; mod mailbox; diff --git a/rust/chains/hyperlane-sealevel/src/mailbox.rs b/rust/chains/hyperlane-sealevel/src/mailbox.rs index 840661b3c9..ab72e4b0f2 100644 --- a/rust/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/chains/hyperlane-sealevel/src/mailbox.rs @@ -69,8 +69,7 @@ pub struct SealevelMailbox { pub(crate) program_id: Pubkey, inbox: (Pubkey, u8), pub(crate) outbox: (Pubkey, u8), - pub(crate) rpc_client: RpcClient, - pub(crate) domain: HyperlaneDomain, + pub(crate) provider: SealevelProvider, payer: Option, } @@ -81,10 +80,7 @@ impl SealevelMailbox { locator: ContractLocator, payer: Option, ) -> ChainResult { - // Set the `processed` commitment at rpc level - let rpc_client = - RpcClient::new_with_commitment(conf.url.to_string(), CommitmentConfig::processed()); - + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); let domain = locator.domain.id(); let inbox = Pubkey::find_program_address(mailbox_inbox_pda_seeds!(), &program_id); @@ -99,8 +95,7 @@ impl SealevelMailbox { program_id, inbox, outbox, - rpc_client, - domain: locator.domain.clone(), + provider, payer, }) } @@ -112,6 +107,10 @@ impl SealevelMailbox { self.outbox } + pub fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } + /// Simulates an instruction, and attempts to deserialize it into a T. /// If no return data at all was returned, returns Ok(None). /// If some return data was returned but deserialization was unsuccesful, @@ -121,7 +120,7 @@ impl SealevelMailbox { instruction: Instruction, ) -> ChainResult> { simulate_instruction( - &self.rpc_client, + &self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -136,7 +135,7 @@ impl SealevelMailbox { instruction: Instruction, ) -> ChainResult> { get_account_metas( - &self.rpc_client, + &self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -263,11 +262,11 @@ impl HyperlaneContract for SealevelMailbox { impl HyperlaneChain for SealevelMailbox { fn domain(&self) -> &HyperlaneDomain { - &self.domain + &self.provider.domain() } fn provider(&self) -> Box { - Box::new(SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -295,7 +294,7 @@ impl Mailbox for SealevelMailbox { ); let account = self - .rpc_client + .rpc() .get_account_with_commitment( &processed_message_account_key, CommitmentConfig::finalized(), @@ -309,7 +308,7 @@ impl Mailbox for SealevelMailbox { #[instrument(err, ret, skip(self))] async fn default_ism(&self) -> ChainResult { let inbox_account = self - .rpc_client + .rpc() .get_account(&self.inbox.0) .await .map_err(ChainCommunicationError::from_other)?; @@ -436,7 +435,7 @@ impl Mailbox for SealevelMailbox { }; instructions.push(inbox_instruction); let (recent_blockhash, _) = self - .rpc_client + .rpc() .get_latest_blockhash_with_commitment(commitment) .await .map_err(ChainCommunicationError::from_other)?; @@ -451,7 +450,7 @@ impl Mailbox for SealevelMailbox { tracing::info!(?txn, "Created sealevel transaction to process message"); let signature = self - .rpc_client + .rpc() .send_and_confirm_transaction(&txn) .await .map_err(ChainCommunicationError::from_other)?; @@ -459,7 +458,7 @@ impl Mailbox for SealevelMailbox { tracing::info!(?txn, ?signature, "Sealevel transaction sent"); let executed = self - .rpc_client + .rpc() .confirm_transaction_with_commitment(&signature, commitment) .await .map_err(|err| warn!("Failed to confirm inbox process transaction: {}", err)) @@ -498,7 +497,6 @@ impl Mailbox for SealevelMailbox { /// Struct that retrieves event data for a Sealevel Mailbox contract #[derive(Debug)] pub struct SealevelMailboxIndexer { - rpc_client: RpcClientWithDebug, mailbox: SealevelMailbox, program_id: Pubkey, } @@ -506,18 +504,20 @@ pub struct SealevelMailboxIndexer { impl SealevelMailboxIndexer { pub fn new(conf: &ConnectionConf, locator: ContractLocator) -> ChainResult { let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); let mailbox = SealevelMailbox::new(conf, locator, None)?; Ok(Self { program_id, - rpc_client, mailbox, }) } + fn rpc(&self) -> &RpcClientWithDebug { + &self.mailbox.rpc() + } + async fn get_finalized_block_number(&self) -> ChainResult { let height = self - .rpc_client + .rpc() .get_block_height() .await .map_err(ChainCommunicationError::from_other)? @@ -560,7 +560,7 @@ impl SealevelMailboxIndexer { with_context: Some(false), }; let accounts = self - .rpc_client + .rpc() .get_program_accounts_with_config(&self.mailbox.program_id, config) .await .map_err(ChainCommunicationError::from_other)?; @@ -595,7 +595,7 @@ impl SealevelMailboxIndexer { // Now that we have the valid message storage PDA pubkey, we can get the full account data. let account = self - .rpc_client + .rpc() .get_account_with_commitment( &valid_message_storage_pda_pubkey, CommitmentConfig::finalized(), @@ -660,7 +660,7 @@ impl Indexer for SealevelMailboxIndexer { } async fn get_finalized_block_number(&self) -> ChainResult { - get_finalized_block_number(&self.rpc_client).await + get_finalized_block_number(&self.rpc()).await } } diff --git a/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs b/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs index 1b15cb5331..e721a82019 100644 --- a/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use derive_new::new; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, - Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, SequenceIndexer, + HyperlaneChain, Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, SequenceIndexer, }; use hyperlane_sealevel_mailbox::accounts::OutboxAccount; use solana_sdk::commitment_config::CommitmentConfig; @@ -22,7 +22,7 @@ impl MerkleTreeHook for SealevelMailbox { ); let outbox_account = self - .rpc_client + .rpc() .get_account_with_commitment(&self.outbox.0, CommitmentConfig::finalized()) .await .map_err(ChainCommunicationError::from_other)? @@ -58,7 +58,7 @@ impl MerkleTreeHook for SealevelMailbox { })?; let checkpoint = Checkpoint { merkle_tree_hook_address: self.program_id.to_bytes().into(), - mailbox_domain: self.domain.id(), + mailbox_domain: self.domain().id(), root, index, }; diff --git a/rust/chains/hyperlane-sealevel/src/multisig_ism.rs b/rust/chains/hyperlane-sealevel/src/multisig_ism.rs index 71cdc7136f..794e19c145 100644 --- a/rust/chains/hyperlane-sealevel/src/multisig_ism.rs +++ b/rust/chains/hyperlane-sealevel/src/multisig_ism.rs @@ -24,25 +24,29 @@ use multisig_ism::interface::{ /// A reference to a MultisigIsm contract on some Sealevel chain #[derive(Debug)] pub struct SealevelMultisigIsm { - rpc_client: RpcClientWithDebug, payer: Option, program_id: Pubkey, domain: HyperlaneDomain, + provider: SealevelProvider, } impl SealevelMultisigIsm { /// Create a new Sealevel MultisigIsm. pub fn new(conf: &ConnectionConf, locator: ContractLocator, payer: Option) -> Self { - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); Self { - rpc_client, payer, program_id, domain: locator.domain.clone(), + provider, } } + + fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } } impl HyperlaneContract for SealevelMultisigIsm { @@ -57,7 +61,7 @@ impl HyperlaneChain for SealevelMultisigIsm { } fn provider(&self) -> Box { - Box::new(SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -84,7 +88,7 @@ impl MultisigIsm for SealevelMultisigIsm { let validators_and_threshold = simulate_instruction::>( - &self.rpc_client, + self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -132,7 +136,7 @@ impl SealevelMultisigIsm { ); get_account_metas( - &self.rpc_client, + self.rpc(), self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, diff --git a/rust/chains/hyperlane-sealevel/src/provider.rs b/rust/chains/hyperlane-sealevel/src/provider.rs index 47be23014c..91908f9168 100644 --- a/rust/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/chains/hyperlane-sealevel/src/provider.rs @@ -1,19 +1,47 @@ +use std::{str::FromStr, sync::Arc}; + use async_trait::async_trait; use hyperlane_core::{ BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, U256, }; +use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; + +use crate::{client::RpcClientWithDebug, error::HyperlaneSealevelError, ConnectionConf}; /// A wrapper around a Sealevel provider to get generic blockchain information. #[derive(Debug)] pub struct SealevelProvider { domain: HyperlaneDomain, + rpc_client: Arc, } impl SealevelProvider { /// Create a new Sealevel provider. - pub fn new(domain: HyperlaneDomain) -> Self { - SealevelProvider { domain } + pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self { + // Set the `processed` commitment at rpc level + let rpc_client = Arc::new(RpcClientWithDebug::new_with_commitment( + conf.url.to_string(), + CommitmentConfig::processed(), + )); + + SealevelProvider { domain, rpc_client } + } + + /// Get an rpc client + pub fn rpc(&self) -> &RpcClientWithDebug { + &self.rpc_client + } + + /// Get the balance of an address + pub async fn get_balance(&self, address: String) -> ChainResult { + let pubkey = Pubkey::from_str(&address).map_err(Into::::into)?; + let balance = self + .rpc_client + .get_balance(&pubkey) + .await + .map_err(Into::::into)?; + Ok(balance.into()) } } @@ -25,6 +53,7 @@ impl HyperlaneChain for SealevelProvider { fn provider(&self) -> Box { Box::new(SealevelProvider { domain: self.domain.clone(), + rpc_client: self.rpc_client.clone(), }) } } @@ -44,7 +73,7 @@ impl HyperlaneProvider for SealevelProvider { Ok(true) } - async fn get_balance(&self, _address: String) -> ChainResult { - todo!() // FIXME + async fn get_balance(&self, address: String) -> ChainResult { + self.get_balance(address).await } } diff --git a/rust/chains/hyperlane-sealevel/src/validator_announce.rs b/rust/chains/hyperlane-sealevel/src/validator_announce.rs index 5fbb470af0..e7fc8dca80 100644 --- a/rust/chains/hyperlane-sealevel/src/validator_announce.rs +++ b/rust/chains/hyperlane-sealevel/src/validator_announce.rs @@ -8,7 +8,7 @@ use hyperlane_core::{ }; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; -use crate::{ConnectionConf, RpcClientWithDebug}; +use crate::{ConnectionConf, RpcClientWithDebug, SealevelProvider}; use hyperlane_sealevel_validator_announce::{ accounts::ValidatorStorageLocationsAccount, validator_storage_locations_pda_seeds, }; @@ -17,21 +17,25 @@ use hyperlane_sealevel_validator_announce::{ #[derive(Debug)] pub struct SealevelValidatorAnnounce { program_id: Pubkey, - rpc_client: RpcClientWithDebug, domain: HyperlaneDomain, + provider: SealevelProvider, } impl SealevelValidatorAnnounce { /// Create a new Sealevel ValidatorAnnounce pub fn new(conf: &ConnectionConf, locator: ContractLocator) -> Self { - let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); + let provider = SealevelProvider::new(locator.domain.clone(), conf); let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); Self { program_id, - rpc_client, domain: locator.domain.clone(), + provider, } } + + fn rpc(&self) -> &RpcClientWithDebug { + self.provider.rpc() + } } impl HyperlaneContract for SealevelValidatorAnnounce { @@ -46,7 +50,7 @@ impl HyperlaneChain for SealevelValidatorAnnounce { } fn provider(&self) -> Box { - Box::new(crate::SealevelProvider::new(self.domain.clone())) + self.provider.provider() } } @@ -74,7 +78,7 @@ impl ValidatorAnnounce for SealevelValidatorAnnounce { // Get all validator storage location accounts. // If an account doesn't exist, it will be returned as None. let accounts = self - .rpc_client + .rpc() .get_multiple_accounts_with_commitment(&account_pubkeys, CommitmentConfig::finalized()) .await .map_err(ChainCommunicationError::from_other)?