Skip to content

Commit

Permalink
wip: igp sealevel indexing
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-savu committed Jul 31, 2023
1 parent 798d5c6 commit a246435
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 13 deletions.
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/chains/hyperlane-sealevel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ url.workspace = true

hyperlane-core = { path = "../../hyperlane-core" }
hyperlane-sealevel-mailbox = { path = "../../sealevel/programs/mailbox", features = ["no-entrypoint"] }
hyperlane-sealevel-igp = { path = "../../sealevel/programs/interchain-gas-paymaster", features = ["no-entrypoint"] }
hyperlane-sealevel-interchain-security-module-interface = { path = "../../sealevel/libraries/interchain-security-module-interface" }
hyperlane-sealevel-message-recipient-interface = { path = "../../sealevel/libraries/message-recipient-interface" }
serializable-account-meta = { path = "../../sealevel/libraries/serializable-account-meta" }
Expand Down
5 changes: 5 additions & 0 deletions rust/chains/hyperlane-sealevel/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;

/// Kludge to implement Debug for RpcClient.
pub(crate) struct RpcClientWithDebug(RpcClient);
Expand All @@ -7,6 +8,10 @@ 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 {
Expand Down
186 changes: 173 additions & 13 deletions rust/chains/hyperlane-sealevel/src/interchain_gas.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,57 @@
#![allow(warnings)] // FIXME remove

use async_trait::async_trait;
use hyperlane_core::{
ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain,
HyperlaneProvider, IndexRange, Indexer, InterchainGasPaymaster, InterchainGasPayment, LogMeta,
H256,
ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneProvider,
IndexRange::{self, SequenceRange},
Indexer, InterchainGasPaymaster, InterchainGasPayment, LogMeta, H256, U256,
};
use hyperlane_sealevel_igp::{
accounts::ProgramDataAccount, igp_pda_seeds, igp_program_data_pda_seeds,
};
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_client::{
nonblocking::rpc_client::RpcClient,
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
};
use tracing::{info, instrument};
use tracing::{debug, info, instrument};

use crate::{ConnectionConf, SealevelProvider};
use solana_sdk::pubkey::Pubkey;
use crate::{client::RpcClientWithDebug, ConnectionConf, SealevelProvider};
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};

/// A reference to an IGP contract on some Sealevel chain
#[derive(Debug)]
pub struct SealevelInterchainGasPaymaster {
program_id: Pubkey,
pda: (Pubkey, u8),
data_pda: (Pubkey, u8),
domain: HyperlaneDomain,
}

impl SealevelInterchainGasPaymaster {
/// Create a new Sealevel IGP.
pub fn new(_conf: &ConnectionConf, locator: ContractLocator) -> Self {
pub fn new(conf: &ConnectionConf, locator: ContractLocator, seed: u64) -> Self {
// Set the `processed` commitment at rpc level
let rpc_client =
RpcClient::new_with_commitment(conf.url.to_string(), CommitmentConfig::processed());

let program_id = Pubkey::from(<[u8; 32]>::from(locator.address));
let domain = locator.domain.id();
// TODO: is this the right way of passing the seed?
let pda = Pubkey::find_program_address(igp_pda_seeds!(seed), &program_id);
let data_pda = Pubkey::find_program_address(igp_program_data_pda_seeds!(), &program_id);

debug!(
"domain={}\nmailbox={}\npda=({}, {})\ndata_pda=({}, {})",
domain, program_id, pda.0, pda.1, data_pda.0, data_pda.1,
);

Self {
program_id,
pda,
data_pda,
domain: locator.domain.clone(),
}
}
Expand All @@ -47,12 +77,128 @@ impl InterchainGasPaymaster for SealevelInterchainGasPaymaster {}

/// Struct that retrieves event data for a Sealevel IGP contract
#[derive(Debug)]
pub struct SealevelInterchainGasPaymasterIndexer {}
pub struct SealevelInterchainGasPaymasterIndexer {
rpc_client: RpcClientWithDebug,
igp: SealevelInterchainGasPaymaster,
program_id: Pubkey,
}

impl SealevelInterchainGasPaymasterIndexer {
/// Create a new Sealevel IGP indexer.
pub fn new(_conf: &ConnectionConf, _locator: ContractLocator) -> Self {
Self {}
pub fn new(conf: &ConnectionConf, locator: ContractLocator) -> Self {
let program_id = Pubkey::from(<[u8; 32]>::from(locator.address));
// Set the `processed` commitment at rpc level
let rpc_client = RpcClientWithDebug::new_with_commitment(
conf.url.to_string(),
CommitmentConfig::processed(),
);
let igp = SealevelInterchainGasPaymaster::new(conf, locator)?;
Ok(Self {
program_id,
rpc_client,
igp,
})
}

async fn get_payment_with_sequence(
&self,
sequence_number: u64,
) -> ChainResult<(InterchainGasPayment, LogMeta)> {
let payment_bytes = &[
&hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR[..],
&sequence_number.to_le_bytes()[..],
]
.concat();
let payment_bytes: String = base64::encode(payment_bytes);

// First, find all accounts with the matching gas payment data.
// To keep responses small in case there is ever more than 1
// match, we don't request the full account data, and just request
// the `unique_gas_payment_pubkey` field.
let memcmp = RpcFilterType::Memcmp(Memcmp {
// Ignore the first byte, which is the `initialized` bool flag.
offset: 1,
bytes: MemcmpEncodedBytes::Base64(payment_bytes),
encoding: None,
});
let config = RpcProgramAccountsConfig {
filters: Some(vec![memcmp]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
// Don't return any data
data_slice: Some(UiDataSliceConfig {
offset: 1 + 8 + 32 + 4 + 32 + 8, // the offset to get the `unique_gas_payment_pubkey` field
length: 32, // the length of the `unique_gas_payment_pubkey` field
}),
commitment: Some(CommitmentConfig::finalized()),
min_context_slot: None,
},
with_context: Some(false),
};
let accounts = self
.rpc_client
.get_program_accounts_with_config(&self.igp.program_id, config)
.await
.map_err(ChainCommunicationError::from_other)?;

// Now loop through matching accounts and find the one with a valid account pubkey
// that proves it's an actual message storage PDA.
let mut valid_payment_pda_pubkey = Option::<Pubkey>::None;

for (pubkey, account) in accounts.iter() {
let unique_message_pubkey = Pubkey::new(&account.data);
let (expected_pubkey, _bump) = Pubkey::try_find_program_address(
// TODO: should the bump be passed as a seed?
igp_program_data_pda_seeds!(),
&self.igp.program_id,
)
.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find program address for unique_gas_payment_pubkey",
)
})?;
if expected_pubkey == *pubkey {
valid_payment_pda_pubkey = Some(*pubkey);
break;
}
}

let valid_payment_pda_pubkey = valid_payment_pda_pubkey.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find valid message storage PDA pubkey",
)
})?;

// Now that we have the valid message storage 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")
})?;
let dispatched_payment_account = ProgramDataAccount::fetch(&mut account.data.as_ref())
.map_err(ChainCommunicationError::from_other)?
.into_inner();
let igp_payment =
// TODO: implement `Decode` for `InterchainGasPayment`
InterchainGasPayment::read_from(&mut &dispatched_payment_account.encoded_message[..])?;

Ok((
igp_payment,
LogMeta {
address: self.igp.program_id.to_bytes().into(),
block_number: dispatched_payment_account.slot,
// TODO: get these when building out scraper support.
// It's inconvenient to get these :|
block_hash: H256::zero(),
transaction_hash: H256::zero(),
transaction_index: 0,
log_index: U256::zero(),
},
))
}
}

Expand All @@ -61,10 +207,24 @@ impl Indexer<InterchainGasPayment> for SealevelInterchainGasPaymasterIndexer {
#[instrument(err, skip(self))]
async fn fetch_logs(
&self,
_range: IndexRange,
range: IndexRange,
) -> ChainResult<Vec<(InterchainGasPayment, LogMeta)>> {
info!("Gas payment indexing not implemented for Sealevel");
Ok(vec![])
let SequenceRange(range) = range else {
return Err(ChainCommunicationError::from_other_str(
"SealevelMailboxIndexer only supports sequence-based indexing",
))
};

info!(
?range,
"Fetching SealevelMailboxIndexer HyperlaneMessage logs"
);

let mut messages = Vec::with_capacity((range.end() - range.start()) as usize);
for nonce in range {
messages.push(self.get_payment_with_sequence(nonce.into()).await?);
}
Ok(messages)
}

#[instrument(level = "debug", err, ret, skip(self))]
Expand Down

0 comments on commit a246435

Please sign in to comment.