diff --git a/rust/main/chains/hyperlane-sealevel/src/client.rs b/rust/main/chains/hyperlane-sealevel/src/client.rs deleted file mode 100644 index cc41cd0b2..000000000 --- a/rust/main/chains/hyperlane-sealevel/src/client.rs +++ /dev/null @@ -1,29 +0,0 @@ -use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::commitment_config::CommitmentConfig; - -/// Kludge to implement Debug for RpcClient. -pub struct RpcClientWithDebug(RpcClient); - -impl RpcClientWithDebug { - pub fn new(rpc_endpoint: String) -> Self { - Self(RpcClient::new(rpc_endpoint)) - } - - pub fn new_with_commitment(rpc_endpoint: String, commitment: CommitmentConfig) -> Self { - Self(RpcClient::new_with_commitment(rpc_endpoint, commitment)) - } -} - -impl std::fmt::Debug for RpcClientWithDebug { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("RpcClient { ... }") - } -} - -impl std::ops::Deref for RpcClientWithDebug { - type Target = RpcClient; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index 8c0972c9a..d2f78eb4b 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -16,9 +16,7 @@ use solana_client::{ use std::ops::RangeInclusive; use tracing::{info, instrument}; -use crate::{ - client::RpcClientWithDebug, utils::get_finalized_block_number, ConnectionConf, SealevelProvider, -}; +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use derive_new::new; @@ -60,20 +58,14 @@ impl SealevelInterchainGasPaymaster { } async fn determine_igp_program_id( - rpc_client: &RpcClientWithDebug, + rpc_client: &SealevelRpcClient, igp_account_pubkey: &H256, ) -> ChainResult { let account = rpc_client - .get_account_with_commitment( - &Pubkey::from(<[u8; 32]>::from(*igp_account_pubkey)), - CommitmentConfig::finalized(), - ) - .await - .map_err(ChainCommunicationError::from_other)? - .value - .ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find IGP account for pubkey") - })?; + .get_account_with_finalized_commitment(&Pubkey::from(<[u8; 32]>::from( + *igp_account_pubkey, + ))) + .await?; Ok(account.owner) } } @@ -99,7 +91,7 @@ impl InterchainGasPaymaster for SealevelInterchainGasPaymaster {} /// Struct that retrieves event data for a Sealevel IGP contract #[derive(Debug)] pub struct SealevelInterchainGasPaymasterIndexer { - rpc_client: RpcClientWithDebug, + rpc_client: SealevelRpcClient, igp: SealevelInterchainGasPaymaster, } @@ -118,10 +110,7 @@ impl SealevelInterchainGasPaymasterIndexer { igp_account_locator: ContractLocator<'_>, ) -> ChainResult { // Set the `processed` commitment at rpc level - let rpc_client = RpcClientWithDebug::new_with_commitment( - conf.url.to_string(), - CommitmentConfig::processed(), - ); + let rpc_client = SealevelRpcClient::new(conf.url.to_string()); let igp = SealevelInterchainGasPaymaster::new(conf, &igp_account_locator).await?; Ok(Self { rpc_client, igp }) @@ -169,8 +158,7 @@ impl SealevelInterchainGasPaymasterIndexer { let accounts = self .rpc_client .get_program_accounts_with_config(&self.igp.program_id, config) - .await - .map_err(ChainCommunicationError::from_other)?; + .await?; tracing::debug!(accounts=?accounts, "Fetched program accounts"); @@ -202,13 +190,8 @@ impl SealevelInterchainGasPaymasterIndexer { // Now that we have the valid gas payment PDA pubkey, we can get the full account data. let account = self .rpc_client - .get_account_with_commitment(&valid_payment_pda_pubkey, CommitmentConfig::finalized()) - .await - .map_err(ChainCommunicationError::from_other)? - .value - .ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find account data") - })?; + .get_account_with_finalized_commitment(&valid_payment_pda_pubkey) + .await?; let gas_payment_account = GasPaymentAccount::fetch(&mut account.data.as_ref()) .map_err(ChainCommunicationError::from_other)? .into_inner(); @@ -274,7 +257,7 @@ impl Indexer for SealevelInterchainGasPaymasterIndexer { #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn get_finalized_block_number(&self) -> ChainResult { - get_finalized_block_number(&self.rpc_client).await + self.rpc_client.get_block_height().await } } @@ -285,13 +268,8 @@ impl SequenceAwareIndexer for SealevelInterchainGasPaymast async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let program_data_account = self .rpc_client - .get_account_with_commitment(&self.igp.data_pda_pubkey, CommitmentConfig::finalized()) - .await - .map_err(ChainCommunicationError::from_other)? - .value - .ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find account data") - })?; + .get_account_with_finalized_commitment(&self.igp.data_pda_pubkey) + .await?; let program_data = ProgramDataAccount::fetch(&mut program_data_account.data.as_ref()) .map_err(ChainCommunicationError::from_other)? .into_inner(); @@ -299,7 +277,7 @@ impl SequenceAwareIndexer for SealevelInterchainGasPaymast .payment_count .try_into() .map_err(StrOrIntParseError::from)?; - let tip = get_finalized_block_number(&self.rpc_client).await?; + let tip = self.rpc_client.get_block_height().await?; Ok((Some(payment_count), tip)) } } diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_security_module.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_security_module.rs index 0f92432eb..aaf2683fd 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_security_module.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_security_module.rs @@ -10,7 +10,7 @@ use hyperlane_core::{ use hyperlane_sealevel_interchain_security_module_interface::InterchainSecurityModuleInstruction; use serializable_account_meta::SimulationReturnData; -use crate::{utils::simulate_instruction, ConnectionConf, RpcClientWithDebug, SealevelProvider}; +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; /// A reference to an InterchainSecurityModule contract on some Sealevel chain #[derive(Debug)] @@ -32,7 +32,7 @@ impl SealevelInterchainSecurityModule { } } - fn rpc(&self) -> &RpcClientWithDebug { + fn rpc(&self) -> &SealevelRpcClient { self.provider.rpc() } } @@ -64,18 +64,19 @@ impl InterchainSecurityModule for SealevelInterchainSecurityModule { vec![], ); - let module = simulate_instruction::>( - self.rpc(), - self.payer - .as_ref() - .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, - instruction, - ) - .await? - .ok_or_else(|| { - ChainCommunicationError::from_other_str("No return data was returned from the ISM") - })? - .return_data; + let module = self + .rpc() + .simulate_instruction::>( + self.payer + .as_ref() + .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, + instruction, + ) + .await? + .ok_or_else(|| { + ChainCommunicationError::from_other_str("No return data was returned from the ISM") + })? + .return_data; if let Some(module_type) = ModuleType::from_u32(module) { Ok(module_type) diff --git a/rust/main/chains/hyperlane-sealevel/src/lib.rs b/rust/main/chains/hyperlane-sealevel/src/lib.rs index 8cd8830f5..04e2218c6 100644 --- a/rust/main/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/main/chains/hyperlane-sealevel/src/lib.rs @@ -5,12 +5,12 @@ #![deny(warnings)] pub use crate::multisig_ism::*; -pub(crate) use client::RpcClientWithDebug; pub use interchain_gas::*; pub use interchain_security_module::*; pub use mailbox::*; pub use merkle_tree_hook::*; pub use provider::*; +pub(crate) use rpc::SealevelRpcClient; pub use solana_sdk::signer::keypair::Keypair; pub use trait_builder::*; pub use validator_announce::*; @@ -22,8 +22,6 @@ mod mailbox; mod merkle_tree_hook; mod multisig_ism; mod provider; +mod rpc; mod trait_builder; -mod utils; - -mod client; mod validator_announce; diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index f101435c2..952599c42 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -8,11 +8,12 @@ use jsonrpc_core::futures_util::TryFutureExt; use tracing::{debug, info, instrument, warn}; use hyperlane_core::{ - accumulator::incremental::IncrementalMerkle, BatchItem, ChainCommunicationError, ChainResult, - Checkpoint, ContractLocator, Decode as _, Encode as _, FixedPointNumber, HyperlaneAbi, - HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, - Indexed, Indexer, KnownHyperlaneDomain, LogMeta, Mailbox, MerkleTreeHook, SequenceAwareIndexer, - TxCostEstimate, TxOutcome, H256, H512, U256, + accumulator::incremental::IncrementalMerkle, BatchItem, ChainCommunicationError, + ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _, + Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, HyperlaneContract, + HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain, + LogMeta, Mailbox, MerkleTreeHook, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512, + U256, }; use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, @@ -54,11 +55,7 @@ use solana_transaction_status::{ UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, }; -use crate::RpcClientWithDebug; -use crate::{ - utils::{get_account_metas, get_finalized_block_number, simulate_instruction}, - ConnectionConf, SealevelProvider, -}; +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; const SPL_NOOP: &str = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"; @@ -128,7 +125,7 @@ impl SealevelMailbox { self.outbox } - pub fn rpc(&self) -> &RpcClientWithDebug { + pub fn rpc(&self) -> &SealevelRpcClient { self.provider.rpc() } @@ -140,14 +137,14 @@ impl SealevelMailbox { &self, instruction: Instruction, ) -> ChainResult> { - simulate_instruction( - &self.rpc(), - self.payer - .as_ref() - .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, - instruction, - ) - .await + self.rpc() + .simulate_instruction( + self.payer + .as_ref() + .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, + instruction, + ) + .await } /// Simulates an Instruction that will return a list of AccountMetas. @@ -155,14 +152,14 @@ impl SealevelMailbox { &self, instruction: Instruction, ) -> ChainResult> { - get_account_metas( - &self.rpc(), - self.payer - .as_ref() - .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, - instruction, - ) - .await + self.rpc() + .get_account_metas( + self.payer + .as_ref() + .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, + instruction, + ) + .await } /// Gets the recipient ISM given a recipient program id and the ISM getter account metas. @@ -293,7 +290,6 @@ impl SealevelMailbox { .rpc() .send_and_confirm_transaction(transaction) .await - .map_err(ChainCommunicationError::from_other) } } @@ -343,13 +339,10 @@ impl SealevelMailbox { ); let recent_blockhash = if transaction.uses_durable_nonce() { - let (recent_blockhash, ..) = self - .provider + self.provider .rpc() .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) - .await - .map_err(ChainCommunicationError::from_other)?; - recent_blockhash + .await? } else { *transaction.get_recent_blockhash() }; @@ -359,8 +352,7 @@ impl SealevelMailbox { .provider .rpc() .get_signature_statuses(&[*signature]) - .await - .map_err(ChainCommunicationError::from_other)?; + .await?; let signature_status = signature_statuses.value.first().cloned().flatten(); match signature_status { Some(_) => return Ok(*signature), @@ -368,9 +360,8 @@ impl SealevelMailbox { if !self .provider .rpc() - .is_blockhash_valid(&recent_blockhash, CommitmentConfig::processed()) - .await - .map_err(ChainCommunicationError::from_other)? + .is_blockhash_valid(&recent_blockhash) + .await? { // Block hash is not found by some reason break 'sending; @@ -439,23 +430,15 @@ impl Mailbox for SealevelMailbox { let account = self .rpc() - .get_account_with_commitment( - &processed_message_account_key, - CommitmentConfig::finalized(), - ) - .await - .map_err(ChainCommunicationError::from_other)?; + .get_possible_account_with_finalized_commitment(&processed_message_account_key) + .await?; - Ok(account.value.is_some()) + Ok(account.is_some()) } #[instrument(err, ret, skip(self))] async fn default_ism(&self) -> ChainResult { - let inbox_account = self - .rpc() - .get_account(&self.inbox.0) - .await - .map_err(ChainCommunicationError::from_other)?; + let inbox_account = self.rpc().get_account(&self.inbox.0).await?; let inbox = InboxAccount::fetch(&mut inbox_account.data.as_ref()) .map_err(ChainCommunicationError::from_other)? .into_inner(); @@ -591,11 +574,10 @@ impl Mailbox for SealevelMailbox { accounts, }; instructions.push(inbox_instruction); - let (recent_blockhash, _) = self + let recent_blockhash = self .rpc() .get_latest_blockhash_with_commitment(commitment) - .await - .map_err(ChainCommunicationError::from_other)?; + .await?; let txn = Transaction::new_signed_with_payer( &instructions, @@ -615,7 +597,6 @@ impl Mailbox for SealevelMailbox { .confirm_transaction_with_commitment(&signature, commitment) .await .map_err(|err| warn!("Failed to confirm inbox process transaction: {}", err)) - .map(|ctx| ctx.value) .unwrap_or(false); let txid = signature.into(); @@ -664,20 +645,12 @@ impl SealevelMailboxIndexer { }) } - fn rpc(&self) -> &RpcClientWithDebug { + fn rpc(&self) -> &SealevelRpcClient { &self.mailbox.rpc() } async fn get_finalized_block_number(&self) -> ChainResult { - let height = self - .rpc() - .get_block_height() - .await - .map_err(ChainCommunicationError::from_other)? - .try_into() - // FIXME solana block height is u64... - .expect("sealevel block height exceeds u32::MAX"); - Ok(height) + self.rpc().get_block_height().await } async fn get_message_with_nonce( @@ -718,8 +691,7 @@ impl SealevelMailboxIndexer { let accounts = self .rpc() .get_program_accounts_with_config(&self.mailbox.program_id, config) - .await - .map_err(ChainCommunicationError::from_other)?; + .await?; // Now loop through matching accounts and find the one with a valid account pubkey // that proves it's an actual message storage PDA. @@ -752,16 +724,8 @@ impl SealevelMailboxIndexer { // Now that we have the valid message storage PDA pubkey, we can get the full account data. let account = self .rpc() - .get_account_with_commitment( - &valid_message_storage_pda_pubkey, - CommitmentConfig::finalized(), - ) - .await - .map_err(ChainCommunicationError::from_other)? - .value - .ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find account data") - })?; + .get_account_with_finalized_commitment(&valid_message_storage_pda_pubkey) + .await?; let dispatched_message_account = DispatchedMessageAccount::fetch(&mut account.data.as_ref()) .map_err(ChainCommunicationError::from_other)? @@ -816,7 +780,7 @@ impl Indexer for SealevelMailboxIndexer { } async fn get_finalized_block_number(&self) -> ChainResult { - get_finalized_block_number(&self.rpc()).await + self.get_finalized_block_number().await } } diff --git a/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs b/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs index 3778627b2..947f7e70a 100644 --- a/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs +++ b/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs @@ -8,7 +8,6 @@ use hyperlane_core::{ MerkleTreeInsertion, SequenceAwareIndexer, }; use hyperlane_sealevel_mailbox::accounts::OutboxAccount; -use solana_sdk::commitment_config::CommitmentConfig; use tracing::instrument; use crate::{SealevelMailbox, SealevelMailboxIndexer}; @@ -25,13 +24,8 @@ impl MerkleTreeHook for SealevelMailbox { let outbox_account = self .rpc() - .get_account_with_commitment(&self.outbox.0, CommitmentConfig::finalized()) - .await - .map_err(ChainCommunicationError::from_other)? - .value - .ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find account data") - })?; + .get_account_with_finalized_commitment(&self.outbox.0) + .await?; let outbox = OutboxAccount::fetch(&mut outbox_account.data.as_ref()) .map_err(ChainCommunicationError::from_other)? .into_inner(); diff --git a/rust/main/chains/hyperlane-sealevel/src/multisig_ism.rs b/rust/main/chains/hyperlane-sealevel/src/multisig_ism.rs index 794e19c14..a3cdb1273 100644 --- a/rust/main/chains/hyperlane-sealevel/src/multisig_ism.rs +++ b/rust/main/chains/hyperlane-sealevel/src/multisig_ism.rs @@ -1,9 +1,9 @@ use async_trait::async_trait; - use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, MultisigIsm, RawHyperlaneMessage, H256, }; +use hyperlane_sealevel_multisig_ism_message_id::instruction::ValidatorsAndThreshold; use serializable_account_meta::SimulationReturnData; use solana_sdk::{ instruction::{AccountMeta, Instruction}, @@ -11,12 +11,8 @@ use solana_sdk::{ signature::Keypair, }; -use crate::{ - utils::{get_account_metas, simulate_instruction}, - ConnectionConf, RpcClientWithDebug, SealevelProvider, -}; +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; -use hyperlane_sealevel_multisig_ism_message_id::instruction::ValidatorsAndThreshold; use multisig_ism::interface::{ MultisigIsmInstruction, VALIDATORS_AND_THRESHOLD_ACCOUNT_METAS_PDA_SEEDS, }; @@ -44,7 +40,7 @@ impl SealevelMultisigIsm { } } - fn rpc(&self) -> &RpcClientWithDebug { + fn rpc(&self) -> &SealevelRpcClient { self.provider.rpc() } } @@ -86,9 +82,9 @@ impl MultisigIsm for SealevelMultisigIsm { account_metas, ); - let validators_and_threshold = - simulate_instruction::>( - self.rpc(), + let validators_and_threshold = self + .rpc() + .simulate_instruction::>( self.payer .as_ref() .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, @@ -135,13 +131,13 @@ impl SealevelMultisigIsm { vec![AccountMeta::new_readonly(account_metas_pda_key, false)], ); - get_account_metas( - self.rpc(), - self.payer - .as_ref() - .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, - instruction, - ) - .await + self.rpc() + .get_account_metas( + self.payer + .as_ref() + .ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, + instruction, + ) + .await } } diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 42b266550..b292d9594 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -6,44 +6,30 @@ use hyperlane_core::{ BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, U256, }; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; +use solana_sdk::pubkey::Pubkey; -use crate::{client::RpcClientWithDebug, error::HyperlaneSealevelError, ConnectionConf}; +use crate::{error::HyperlaneSealevelError, ConnectionConf, SealevelRpcClient}; /// A wrapper around a Sealevel provider to get generic blockchain information. #[derive(Debug)] pub struct SealevelProvider { domain: HyperlaneDomain, - rpc_client: Arc, + rpc_client: Arc, } impl SealevelProvider { /// Create a new Sealevel provider. 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(), - )); + let rpc_client = Arc::new(SealevelRpcClient::new(conf.url.to_string())); SealevelProvider { domain, rpc_client } } /// Get an rpc client - pub fn rpc(&self) -> &RpcClientWithDebug { + pub fn rpc(&self) -> &SealevelRpcClient { &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()) - } } impl HyperlaneChain for SealevelProvider { @@ -75,7 +61,8 @@ impl HyperlaneProvider for SealevelProvider { } async fn get_balance(&self, address: String) -> ChainResult { - self.get_balance(address).await + let pubkey = Pubkey::from_str(&address).map_err(Into::::into)?; + self.rpc_client.get_balance(&pubkey).await } async fn get_chain_metrics(&self) -> ChainResult> { diff --git a/rust/main/chains/hyperlane-sealevel/src/rpc.rs b/rust/main/chains/hyperlane-sealevel/src/rpc.rs new file mode 100644 index 000000000..1c82b77c0 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/rpc.rs @@ -0,0 +1,3 @@ +pub use client::SealevelRpcClient; + +mod client; diff --git a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs new file mode 100644 index 000000000..77f21ee1f --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs @@ -0,0 +1,242 @@ +use base64::Engine; +use borsh::{BorshDeserialize, BorshSerialize}; +use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; +use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData}; +use solana_client::{ + nonblocking::rpc_client::RpcClient, rpc_config::RpcProgramAccountsConfig, + rpc_response::Response, +}; +use solana_sdk::{ + account::Account, + commitment_config::CommitmentConfig, + hash::Hash, + instruction::{AccountMeta, Instruction}, + message::Message, + pubkey::Pubkey, + signature::{Keypair, Signature, Signer}, + transaction::Transaction, +}; +use solana_transaction_status::{TransactionStatus, UiReturnDataEncoding, UiTransactionReturnData}; + +use crate::error::HyperlaneSealevelError; + +pub struct SealevelRpcClient(RpcClient); + +impl SealevelRpcClient { + pub fn new(rpc_endpoint: String) -> Self { + Self(RpcClient::new_with_commitment( + rpc_endpoint, + CommitmentConfig::processed(), + )) + } + + pub async fn confirm_transaction_with_commitment( + &self, + signature: &Signature, + commitment: CommitmentConfig, + ) -> ChainResult { + self.0 + .confirm_transaction_with_commitment(signature, commitment) + .await + .map(|ctx| ctx.value) + .map_err(HyperlaneSealevelError::ClientError) + .map_err(Into::into) + } + + pub async fn get_account(&self, pubkey: &Pubkey) -> ChainResult { + self.0 + .get_account(pubkey) + .await + .map_err(ChainCommunicationError::from_other) + } + + /// Simulates an Instruction that will return a list of AccountMetas. + pub async fn get_account_metas( + &self, + payer: &Keypair, + instruction: Instruction, + ) -> ChainResult> { + // If there's no data at all, default to an empty vec. + let account_metas = self + .simulate_instruction::>>( + payer, + instruction, + ) + .await? + .map(|serializable_account_metas| { + serializable_account_metas + .return_data + .into_iter() + .map(|serializable_account_meta| serializable_account_meta.into()) + .collect() + }) + .unwrap_or_else(Vec::new); + + Ok(account_metas) + } + + pub async fn get_account_with_finalized_commitment( + &self, + pubkey: &Pubkey, + ) -> ChainResult { + self.get_possible_account_with_finalized_commitment(pubkey) + .await? + .ok_or_else(|| ChainCommunicationError::from_other_str("Could not find account data")) + } + + pub async fn get_possible_account_with_finalized_commitment( + &self, + pubkey: &Pubkey, + ) -> ChainResult> { + let account = self + .0 + .get_account_with_commitment(pubkey, CommitmentConfig::finalized()) + .await + .map_err(ChainCommunicationError::from_other)? + .value; + Ok(account) + } + + pub async fn get_block_height(&self) -> ChainResult { + let height = self + .0 + .get_block_height_with_commitment(CommitmentConfig::finalized()) + .await + .map_err(ChainCommunicationError::from_other)? + .try_into() + // FIXME solana block height is u64... + .expect("sealevel block height exceeds u32::MAX"); + Ok(height) + } + + pub async fn get_multiple_accounts_with_finalized_commitment( + &self, + pubkeys: &[Pubkey], + ) -> ChainResult>> { + let accounts = self + .0 + .get_multiple_accounts_with_commitment(pubkeys, CommitmentConfig::finalized()) + .await + .map_err(ChainCommunicationError::from_other)? + .value; + + Ok(accounts) + } + + pub async fn get_latest_blockhash_with_commitment( + &self, + commitment: CommitmentConfig, + ) -> ChainResult { + self.0 + .get_latest_blockhash_with_commitment(commitment) + .await + .map_err(ChainCommunicationError::from_other) + .map(|(blockhash, _)| blockhash) + } + + pub async fn get_program_accounts_with_config( + &self, + pubkey: &Pubkey, + config: RpcProgramAccountsConfig, + ) -> ChainResult> { + self.0 + .get_program_accounts_with_config(pubkey, config) + .await + .map_err(ChainCommunicationError::from_other) + } + + pub async fn get_signature_statuses( + &self, + signatures: &[Signature], + ) -> ChainResult>>> { + self.0 + .get_signature_statuses(signatures) + .await + .map_err(ChainCommunicationError::from_other) + } + + pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult { + let balance = self + .0 + .get_balance(pubkey) + .await + .map_err(Into::::into) + .map_err(ChainCommunicationError::from)?; + + Ok(balance.into()) + } + + pub async fn is_blockhash_valid(&self, hash: &Hash) -> ChainResult { + self.0 + .is_blockhash_valid(hash, CommitmentConfig::processed()) + .await + .map_err(ChainCommunicationError::from_other) + } + + pub async fn send_and_confirm_transaction( + &self, + transaction: &Transaction, + ) -> ChainResult { + self.0 + .send_and_confirm_transaction(transaction) + .await + .map_err(ChainCommunicationError::from_other) + } + + /// 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 unsuccessful, + /// an Err is returned. + pub async fn simulate_instruction( + &self, + payer: &Keypair, + instruction: Instruction, + ) -> ChainResult> { + let commitment = CommitmentConfig::finalized(); + let recent_blockhash = self + .get_latest_blockhash_with_commitment(commitment) + .await?; + let transaction = Transaction::new_unsigned(Message::new_with_blockhash( + &[instruction], + Some(&payer.pubkey()), + &recent_blockhash, + )); + let return_data = self.simulate_transaction(&transaction).await?; + + if let Some(return_data) = return_data { + let bytes = match return_data.data.1 { + UiReturnDataEncoding::Base64 => base64::engine::general_purpose::STANDARD + .decode(return_data.data.0) + .map_err(ChainCommunicationError::from_other)?, + }; + + let decoded_data = + T::try_from_slice(bytes.as_slice()).map_err(ChainCommunicationError::from_other)?; + + return Ok(Some(decoded_data)); + } + + Ok(None) + } + + async fn simulate_transaction( + &self, + transaction: &Transaction, + ) -> ChainResult> { + let return_data = self + .0 + .simulate_transaction(transaction) + .await + .map_err(ChainCommunicationError::from_other)? + .value + .return_data; + + Ok(return_data) + } +} + +impl std::fmt::Debug for SealevelRpcClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("RpcClient { ... }") + } +} diff --git a/rust/main/chains/hyperlane-sealevel/src/utils.rs b/rust/main/chains/hyperlane-sealevel/src/utils.rs deleted file mode 100644 index 56bec9e51..000000000 --- a/rust/main/chains/hyperlane-sealevel/src/utils.rs +++ /dev/null @@ -1,93 +0,0 @@ -use base64::Engine; -use borsh::{BorshDeserialize, BorshSerialize}; -use hyperlane_core::{ChainCommunicationError, ChainResult}; - -use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData}; -use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::{ - commitment_config::CommitmentConfig, - instruction::{AccountMeta, Instruction}, - message::Message, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use solana_transaction_status::UiReturnDataEncoding; - -use crate::client::RpcClientWithDebug; - -/// 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 unsuccessful, -/// an Err is returned. -pub async fn simulate_instruction( - rpc_client: &RpcClient, - payer: &Keypair, - instruction: Instruction, -) -> ChainResult> { - let commitment = CommitmentConfig::finalized(); - let (recent_blockhash, _) = rpc_client - .get_latest_blockhash_with_commitment(commitment) - .await - .map_err(ChainCommunicationError::from_other)?; - let return_data = rpc_client - .simulate_transaction(&Transaction::new_unsigned(Message::new_with_blockhash( - &[instruction], - Some(&payer.pubkey()), - &recent_blockhash, - ))) - .await - .map_err(ChainCommunicationError::from_other)? - .value - .return_data; - - if let Some(return_data) = return_data { - let bytes = match return_data.data.1 { - UiReturnDataEncoding::Base64 => base64::engine::general_purpose::STANDARD - .decode(return_data.data.0) - .map_err(ChainCommunicationError::from_other)?, - }; - - let decoded_data = - T::try_from_slice(bytes.as_slice()).map_err(ChainCommunicationError::from_other)?; - - return Ok(Some(decoded_data)); - } - - Ok(None) -} - -/// Simulates an Instruction that will return a list of AccountMetas. -pub async fn get_account_metas( - rpc_client: &RpcClient, - payer: &Keypair, - instruction: Instruction, -) -> ChainResult> { - // If there's no data at all, default to an empty vec. - let account_metas = simulate_instruction::>>( - rpc_client, - payer, - instruction, - ) - .await? - .map(|serializable_account_metas| { - serializable_account_metas - .return_data - .into_iter() - .map(|serializable_account_meta| serializable_account_meta.into()) - .collect() - }) - .unwrap_or_else(Vec::new); - - Ok(account_metas) -} - -pub async fn get_finalized_block_number(rpc_client: &RpcClientWithDebug) -> ChainResult { - let height = rpc_client - .get_block_height() - .await - .map_err(ChainCommunicationError::from_other)? - .try_into() - // FIXME solana block height is u64... - .expect("sealevel block height exceeds u32::MAX"); - Ok(height) -} diff --git a/rust/main/chains/hyperlane-sealevel/src/validator_announce.rs b/rust/main/chains/hyperlane-sealevel/src/validator_announce.rs index 52b19495a..3edfa0d06 100644 --- a/rust/main/chains/hyperlane-sealevel/src/validator_announce.rs +++ b/rust/main/chains/hyperlane-sealevel/src/validator_announce.rs @@ -1,17 +1,15 @@ use async_trait::async_trait; -use tracing::{info, instrument, warn}; - use hyperlane_core::{ - Announcement, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, - HyperlaneContract, HyperlaneDomain, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, H512, - U256, + Announcement, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + SignedType, TxOutcome, ValidatorAnnounce, H160, H256, H512, U256, }; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; - -use crate::{ConnectionConf, RpcClientWithDebug, SealevelProvider}; use hyperlane_sealevel_validator_announce::{ accounts::ValidatorStorageLocationsAccount, validator_storage_locations_pda_seeds, }; +use solana_sdk::pubkey::Pubkey; +use tracing::{info, instrument, warn}; + +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; /// A reference to a ValidatorAnnounce contract on some Sealevel chain #[derive(Debug)] @@ -33,7 +31,7 @@ impl SealevelValidatorAnnounce { } } - fn rpc(&self) -> &RpcClientWithDebug { + fn rpc(&self) -> &SealevelRpcClient { self.provider.rpc() } } @@ -79,10 +77,8 @@ impl ValidatorAnnounce for SealevelValidatorAnnounce { // If an account doesn't exist, it will be returned as None. let accounts = self .rpc() - .get_multiple_accounts_with_commitment(&account_pubkeys, CommitmentConfig::finalized()) - .await - .map_err(ChainCommunicationError::from_other)? - .value; + .get_multiple_accounts_with_finalized_commitment(&account_pubkeys) + .await?; // Parse the storage locations from each account. // If a validator's account doesn't exist, its storage locations will