refactor: Make Sealevel RPC client more functional (#4699)

### Description

Make Sealevel RPC client more functional. Move some methods and error
mapping into RPC client.

### Backward compatibility

Yes

### Testing

E2E Ethereum + Sealevel test

---------

Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com>
pull/4720/head
Danil Nemirovsky 1 week ago committed by GitHub
parent 02a5b92ba7
commit 5300230c44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 29
      rust/main/chains/hyperlane-sealevel/src/client.rs
  2. 52
      rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs
  3. 29
      rust/main/chains/hyperlane-sealevel/src/interchain_security_module.rs
  4. 6
      rust/main/chains/hyperlane-sealevel/src/lib.rs
  5. 118
      rust/main/chains/hyperlane-sealevel/src/mailbox.rs
  6. 10
      rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs
  7. 32
      rust/main/chains/hyperlane-sealevel/src/multisig_ism.rs
  8. 27
      rust/main/chains/hyperlane-sealevel/src/provider.rs
  9. 3
      rust/main/chains/hyperlane-sealevel/src/rpc.rs
  10. 242
      rust/main/chains/hyperlane-sealevel/src/rpc/client.rs
  11. 93
      rust/main/chains/hyperlane-sealevel/src/utils.rs
  12. 22
      rust/main/chains/hyperlane-sealevel/src/validator_announce.rs

@ -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
}
}

@ -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<Pubkey> {
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<Self> {
// 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<InterchainGasPayment> 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<u32> {
get_finalized_block_number(&self.rpc_client).await
self.rpc_client.get_block_height().await
}
}
@ -285,13 +268,8 @@ impl SequenceAwareIndexer<InterchainGasPayment> for SealevelInterchainGasPaymast
async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option<u32>, 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<InterchainGasPayment> 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))
}
}

@ -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::<SimulationReturnData<u32>>(
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::<SimulationReturnData<u32>>(
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)

@ -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;

@ -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<Option<T>> {
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<Vec<AccountMeta>> {
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<H256> {
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<u32> {
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<HyperlaneMessage> for SealevelMailboxIndexer {
}
async fn get_finalized_block_number(&self) -> ChainResult<u32> {
get_finalized_block_number(&self.rpc()).await
self.get_finalized_block_number().await
}
}

@ -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();

@ -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::<SimulationReturnData<ValidatorsAndThreshold>>(
self.rpc(),
let validators_and_threshold = self
.rpc()
.simulate_instruction::<SimulationReturnData<ValidatorsAndThreshold>>(
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
}
}

@ -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<RpcClientWithDebug>,
rpc_client: Arc<SealevelRpcClient>,
}
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<U256> {
let pubkey = Pubkey::from_str(&address).map_err(Into::<HyperlaneSealevelError>::into)?;
let balance = self
.rpc_client
.get_balance(&pubkey)
.await
.map_err(Into::<HyperlaneSealevelError>::into)?;
Ok(balance.into())
}
}
impl HyperlaneChain for SealevelProvider {
@ -75,7 +61,8 @@ impl HyperlaneProvider for SealevelProvider {
}
async fn get_balance(&self, address: String) -> ChainResult<U256> {
self.get_balance(address).await
let pubkey = Pubkey::from_str(&address).map_err(Into::<HyperlaneSealevelError>::into)?;
self.rpc_client.get_balance(&pubkey).await
}
async fn get_chain_metrics(&self) -> ChainResult<Option<ChainInfo>> {

@ -0,0 +1,3 @@
pub use client::SealevelRpcClient;
mod client;

@ -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<bool> {
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<Account> {
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<Vec<AccountMeta>> {
// If there's no data at all, default to an empty vec.
let account_metas = self
.simulate_instruction::<SimulationReturnData<Vec<SerializableAccountMeta>>>(
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<Account> {
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<Option<Account>> {
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<u32> {
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<Vec<Option<Account>>> {
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<Hash> {
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<Vec<(Pubkey, Account)>> {
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<Response<Vec<Option<TransactionStatus>>>> {
self.0
.get_signature_statuses(signatures)
.await
.map_err(ChainCommunicationError::from_other)
}
pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult<U256> {
let balance = self
.0
.get_balance(pubkey)
.await
.map_err(Into::<HyperlaneSealevelError>::into)
.map_err(ChainCommunicationError::from)?;
Ok(balance.into())
}
pub async fn is_blockhash_valid(&self, hash: &Hash) -> ChainResult<bool> {
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<Signature> {
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<T: BorshDeserialize + BorshSerialize>(
&self,
payer: &Keypair,
instruction: Instruction,
) -> ChainResult<Option<T>> {
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<Option<UiTransactionReturnData>> {
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 { ... }")
}
}

@ -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<T: BorshDeserialize + BorshSerialize>(
rpc_client: &RpcClient,
payer: &Keypair,
instruction: Instruction,
) -> ChainResult<Option<T>> {
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<Vec<AccountMeta>> {
// If there's no data at all, default to an empty vec.
let account_metas = simulate_instruction::<SimulationReturnData<Vec<SerializableAccountMeta>>>(
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<u32> {
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)
}

@ -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

Loading…
Cancel
Save