From 70a162319687975da14e49c780def5a10caa2e2f Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:56:59 +0100 Subject: [PATCH 01/26] Initial population of delivered messages --- .../chains/hyperlane-sealevel/src/mailbox.rs | 160 +++++++++++++----- 1 file changed, 122 insertions(+), 38 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 5e348d9995..49bbd98e68 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -7,6 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jsonrpc_core::futures_util::TryFutureExt; use tracing::{debug, info, instrument, warn}; +use account_utils::AccountData; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, BatchItem, ChainCommunicationError, ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _, @@ -19,7 +20,10 @@ use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, }; use hyperlane_sealevel_mailbox::{ - accounts::{DispatchedMessageAccount, InboxAccount, OutboxAccount}, + accounts::{ + DispatchedMessageAccount, InboxAccount, OutboxAccount, ProcessedMessage, + ProcessedMessageAccount, + }, instruction::InboxProcess, mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, mailbox_process_authority_pda_seeds, mailbox_processed_message_pda_seeds, @@ -657,41 +661,8 @@ impl SealevelMailboxIndexer { &self, nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { - let target_message_account_bytes = &[ - &hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR[..], - &nonce.to_le_bytes()[..], - ] - .concat(); - let target_message_account_bytes = base64::encode(target_message_account_bytes); - - // First, find all accounts with the matching account 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_message_pubkey` field. - let memcmp = RpcFilterType::Memcmp(Memcmp { - // Ignore the first byte, which is the `initialized` bool flag. - offset: 1, - bytes: MemcmpEncodedBytes::Base64(target_message_account_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 + 4 + 8, // the offset to get the `unique_message_pubkey` field - length: 32, // the length of the `unique_message_pubkey` field - }), - commitment: Some(CommitmentConfig::finalized()), - min_context_slot: None, - }, - with_context: Some(false), - }; - let accounts = self - .rpc() - .get_program_accounts_with_config(&self.mailbox.program_id, config) - .await?; + let discriminator = hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR; + let accounts = self.search_accounts(&discriminator, nonce).await?; // Now loop through matching accounts and find the one with a valid account pubkey // that proves it's an actual message storage PDA. @@ -747,6 +718,109 @@ impl SealevelMailboxIndexer { }, )) } + + async fn get_delivered_message_with_nonce( + &self, + nonce: u32, + ) -> ChainResult<(Indexed, LogMeta)> { + let discriminator = hyperlane_sealevel_mailbox::accounts::PROCESSED_MESSAGE_DISCRIMINATOR; + let accounts = self.search_accounts(&discriminator, nonce).await?; + + debug!(account_len = ?accounts.len(), "Found accounts with processed message discriminator"); + + // Now we loop through all the accounts with the discriminator and get their full account data + // Since the account identifier was created using message id we cannot verify if the + // account is actually the one we need until we get the content (namely message id) and + // check if it was used to create the account id. + let mut valid_processed_message_account = None; + + for (pubkey, _) in accounts { + let account = self + .rpc() + .get_account_with_finalized_commitment(&pubkey) + .await?; + + let processed_message_account = + match ProcessedMessageAccount::fetch(&mut account.data.as_ref()) { + Ok(account) => account.into_inner(), + Err(_) => continue, + }; + + let message_id = processed_message_account.message_id; + + let expected_pubkey = match Pubkey::try_find_program_address( + mailbox_processed_message_pda_seeds!(message_id), + &self.mailbox.program_id, + ) { + Some((expected_pubkey, _bump)) => expected_pubkey, + None => continue, + }; + + if expected_pubkey == pubkey { + valid_processed_message_account = Some(processed_message_account); + break; + } + } + + let valid = valid_processed_message_account.ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find valid deliveled message storage PDA", + ) + })?; + + Ok(( + valid.message_id.into(), + LogMeta { + address: self.mailbox.program_id.to_bytes().into(), + block_number: valid.slot, + // TODO: get these when building out scraper support. + // It's inconvenient to get these :| + block_hash: H256::zero(), + transaction_id: H512::zero(), + transaction_index: 0, + log_index: U256::zero(), + }, + )) + } + + async fn search_accounts( + &self, + discriminator: &[u8; 8], + nonce: u32, + ) -> Result, ChainCommunicationError> { + let target_message_account_bytes = &[&discriminator[..], &nonce.to_le_bytes()[..]].concat(); + let target_message_account_bytes = base64::encode(target_message_account_bytes); + + // First, find all accounts with the matching account 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_message_pubkey` field. + let memcmp = RpcFilterType::Memcmp(Memcmp { + // Ignore the first byte, which is the `initialized` bool flag. + offset: 1, + bytes: MemcmpEncodedBytes::Base64(target_message_account_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 + 4 + 8, // the offset to get the `unique_message_pubkey` field + length: 32, // the length of the `unique_message_pubkey` field + }), + commitment: Some(CommitmentConfig::finalized()), + min_context_slot: None, + }, + with_context: Some(false), + }; + let accounts = self + .rpc() + .get_program_accounts_with_config(&self.mailbox.program_id, config) + .await?; + Ok(accounts) + } } #[async_trait] @@ -788,9 +862,19 @@ impl Indexer for SealevelMailboxIndexer { impl Indexer for SealevelMailboxIndexer { async fn fetch_logs_in_range( &self, - _range: RangeInclusive, + range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - todo!() + info!( + ?range, + "Fetching SealevelMailboxIndexer HyperlaneMessage Delivery logs" + ); + + let message_capacity = range.end().saturating_sub(*range.start()); + let mut message_ids = Vec::with_capacity(message_capacity as usize); + for nonce in range { + message_ids.push(self.get_delivered_message_with_nonce(nonce).await?); + } + Ok(message_ids) } async fn get_finalized_block_number(&self) -> ChainResult { From d9716c4b6798ad0bc22e04929794f6e6442c08b5 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:14:33 +0100 Subject: [PATCH 02/26] Add processing for delivered messages --- .../chains/hyperlane-sealevel/src/mailbox.rs | 167 +++++++++--------- 1 file changed, 87 insertions(+), 80 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 49bbd98e68..7035fc8eea 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -4,10 +4,6 @@ use std::{collections::HashMap, num::NonZeroU64, ops::RangeInclusive, str::FromS use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; -use jsonrpc_core::futures_util::TryFutureExt; -use tracing::{debug, info, instrument, warn}; - -use account_utils::AccountData; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, BatchItem, ChainCommunicationError, ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _, @@ -31,6 +27,7 @@ use hyperlane_sealevel_mailbox::{ use hyperlane_sealevel_message_recipient_interface::{ HandleInstruction, MessageRecipientInstruction, }; +use jsonrpc_core::futures_util::TryFutureExt; use serializable_account_meta::SimulationReturnData; use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; use solana_client::{ @@ -58,6 +55,7 @@ use solana_transaction_status::{ UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, UiReturnDataEncoding, UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, }; +use tracing::{debug, info, instrument, warn}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; @@ -657,39 +655,20 @@ impl SealevelMailboxIndexer { self.rpc().get_block_height().await } - async fn get_message_with_nonce( + async fn get_dispatched_message_with_nonce( &self, nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { let discriminator = hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR; - let accounts = self.search_accounts(&discriminator, nonce).await?; - - // 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_message_storage_pda_pubkey = Option::::None; - - for (pubkey, account) in accounts { - let unique_message_pubkey = Pubkey::new(&account.data); - let (expected_pubkey, _bump) = Pubkey::try_find_program_address( - mailbox_dispatched_message_pda_seeds!(unique_message_pubkey), - &self.mailbox.program_id, - ) - .ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find program address for unique_message_pubkey", - ) - })?; - if expected_pubkey == pubkey { - valid_message_storage_pda_pubkey = Some(pubkey); - break; - } - } + let offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field + let length = 32; // the length of the `unique_message_pubkey` field + let accounts = self + .search_accounts_by_descriminator(&discriminator, nonce, offset, length) + .await?; - let valid_message_storage_pda_pubkey = - valid_message_storage_pda_pubkey.ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find valid message storage PDA pubkey", - ) + let valid_message_storage_pda_pubkey = self + .search_and_validate_account(accounts, |account| { + self.dispatched_message_account(&account) })?; // Now that we have the valid message storage PDA pubkey, we can get the full account data. @@ -719,60 +698,54 @@ impl SealevelMailboxIndexer { )) } + fn dispatched_message_account(&self, account: &Account) -> ChainResult { + let unique_message_pubkey = Pubkey::new(&account.data); + let (expected_pubkey, _bump) = Pubkey::try_find_program_address( + mailbox_dispatched_message_pda_seeds!(unique_message_pubkey), + &self.mailbox.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find program address for unique message pubkey", + ) + })?; + Ok(expected_pubkey) + } + async fn get_delivered_message_with_nonce( &self, nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { let discriminator = hyperlane_sealevel_mailbox::accounts::PROCESSED_MESSAGE_DISCRIMINATOR; - let accounts = self.search_accounts(&discriminator, nonce).await?; + let offset = 1 + 8 + 8; // the offset to get the `message_id` field + let length = 32; + let accounts = self + .search_accounts_by_descriminator(&discriminator, nonce, offset, length) + .await?; debug!(account_len = ?accounts.len(), "Found accounts with processed message discriminator"); - // Now we loop through all the accounts with the discriminator and get their full account data - // Since the account identifier was created using message id we cannot verify if the - // account is actually the one we need until we get the content (namely message id) and - // check if it was used to create the account id. - let mut valid_processed_message_account = None; - - for (pubkey, _) in accounts { - let account = self - .rpc() - .get_account_with_finalized_commitment(&pubkey) - .await?; - - let processed_message_account = - match ProcessedMessageAccount::fetch(&mut account.data.as_ref()) { - Ok(account) => account.into_inner(), - Err(_) => continue, - }; - - let message_id = processed_message_account.message_id; - - let expected_pubkey = match Pubkey::try_find_program_address( - mailbox_processed_message_pda_seeds!(message_id), - &self.mailbox.program_id, - ) { - Some((expected_pubkey, _bump)) => expected_pubkey, - None => continue, - }; - - if expected_pubkey == pubkey { - valid_processed_message_account = Some(processed_message_account); - break; - } - } + let valid_message_storage_pda_pubkey = self + .search_and_validate_account(accounts, |account| { + self.delivered_message_account(&account) + })?; - let valid = valid_processed_message_account.ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find valid deliveled message storage PDA", - ) - })?; + // Now that we have the valid delivered message storage PDA pubkey, + // we can get the full account data. + let account = self + .rpc() + .get_account_with_finalized_commitment(&valid_message_storage_pda_pubkey) + .await?; + let delivered_message_account = ProcessedMessageAccount::fetch(&mut account.data.as_ref()) + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + let message_id = delivered_message_account.message_id; Ok(( - valid.message_id.into(), + message_id.into(), LogMeta { address: self.mailbox.program_id.to_bytes().into(), - block_number: valid.slot, + block_number: delivered_message_account.slot, // TODO: get these when building out scraper support. // It's inconvenient to get these :| block_hash: H256::zero(), @@ -783,10 +756,48 @@ impl SealevelMailboxIndexer { )) } - async fn search_accounts( + fn delivered_message_account(&self, account: &Account) -> ChainResult { + let message_id = H256::from_slice(&account.data); + let (expected_pubkey, _bump) = Pubkey::try_find_program_address( + mailbox_processed_message_pda_seeds!(message_id), + &self.mailbox.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str("Could not find program address for message id") + })?; + Ok(expected_pubkey) + } + + fn search_and_validate_account( + &self, + accounts: Vec<(Pubkey, Account)>, + message_account: F, + ) -> ChainResult + where + F: Fn(&Account) -> ChainResult, + { + let mut valid_storage_pda_pubkey = Option::::None; + + for (pubkey, account) in accounts { + let expected_pubkey = message_account(&account)?; + if expected_pubkey == pubkey { + valid_storage_pda_pubkey = Some(pubkey); + break; + } + } + + let valid_storage_pda_pubkey = valid_storage_pda_pubkey.ok_or_else(|| { + ChainCommunicationError::from_other_str("Could not find valid storage PDA pubkey") + })?; + Ok(valid_storage_pda_pubkey) + } + + async fn search_accounts_by_descriminator( &self, discriminator: &[u8; 8], nonce: u32, + offset: usize, + length: usize, ) -> Result, ChainCommunicationError> { let target_message_account_bytes = &[&discriminator[..], &nonce.to_le_bytes()[..]].concat(); let target_message_account_bytes = base64::encode(target_message_account_bytes); @@ -805,11 +816,7 @@ impl SealevelMailboxIndexer { filters: Some(vec![memcmp]), account_config: RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::Base64), - // Don't return any data - data_slice: Some(UiDataSliceConfig { - offset: 1 + 8 + 4 + 8, // the offset to get the `unique_message_pubkey` field - length: 32, // the length of the `unique_message_pubkey` field - }), + data_slice: Some(UiDataSliceConfig { offset, length }), commitment: Some(CommitmentConfig::finalized()), min_context_slot: None, }, @@ -848,7 +855,7 @@ impl Indexer for SealevelMailboxIndexer { let message_capacity = range.end().saturating_sub(*range.start()); let mut messages = Vec::with_capacity(message_capacity as usize); for nonce in range { - messages.push(self.get_message_with_nonce(nonce).await?); + messages.push(self.get_dispatched_message_with_nonce(nonce).await?); } Ok(messages) } From 474ffff26f4f0b357d10b7125691c6e478ba4dea Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:50:41 +0100 Subject: [PATCH 03/26] Rename method --- rust/main/chains/hyperlane-sealevel/src/mailbox.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 7035fc8eea..762e49d403 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -663,7 +663,7 @@ impl SealevelMailboxIndexer { let offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field let length = 32; // the length of the `unique_message_pubkey` field let accounts = self - .search_accounts_by_descriminator(&discriminator, nonce, offset, length) + .search_accounts_by_discriminator(&discriminator, nonce, offset, length) .await?; let valid_message_storage_pda_pubkey = self @@ -720,7 +720,7 @@ impl SealevelMailboxIndexer { let offset = 1 + 8 + 8; // the offset to get the `message_id` field let length = 32; let accounts = self - .search_accounts_by_descriminator(&discriminator, nonce, offset, length) + .search_accounts_by_discriminator(&discriminator, nonce, offset, length) .await?; debug!(account_len = ?accounts.len(), "Found accounts with processed message discriminator"); @@ -792,7 +792,7 @@ impl SealevelMailboxIndexer { Ok(valid_storage_pda_pubkey) } - async fn search_accounts_by_descriminator( + async fn search_accounts_by_discriminator( &self, discriminator: &[u8; 8], nonce: u32, From c9048b47b6d0b6aad55a18431f5e737b463cf795 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:14:41 +0100 Subject: [PATCH 04/26] Add search for dispatch message transaction in block --- .../chains/hyperlane-sealevel/src/error.rs | 6 ++ .../main/chains/hyperlane-sealevel/src/lib.rs | 2 + .../chains/hyperlane-sealevel/src/mailbox.rs | 52 +++++++++- .../chains/hyperlane-sealevel/src/provider.rs | 14 +-- .../hyperlane-sealevel/src/transaction.rs | 98 +++++++++++++++++++ .../chains/hyperlane-sealevel/src/utils.rs | 33 +++++++ 6 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 rust/main/chains/hyperlane-sealevel/src/transaction.rs create mode 100644 rust/main/chains/hyperlane-sealevel/src/utils.rs diff --git a/rust/main/chains/hyperlane-sealevel/src/error.rs b/rust/main/chains/hyperlane-sealevel/src/error.rs index ff0142c393..00d2ac2e03 100644 --- a/rust/main/chains/hyperlane-sealevel/src/error.rs +++ b/rust/main/chains/hyperlane-sealevel/src/error.rs @@ -17,6 +17,12 @@ pub enum HyperlaneSealevelError { /// Decoding error #[error("{0}")] Decoding(#[from] solana_sdk::bs58::decode::Error), + /// No transaction in block error + #[error("{0}")] + NoTransactions(String), + /// Too many transactions of particular content in block + #[error("{0}")] + TooManyTransactions(String), } impl From for ChainCommunicationError { diff --git a/rust/main/chains/hyperlane-sealevel/src/lib.rs b/rust/main/chains/hyperlane-sealevel/src/lib.rs index 04e2218c6b..f40fd040c9 100644 --- a/rust/main/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/main/chains/hyperlane-sealevel/src/lib.rs @@ -24,4 +24,6 @@ mod multisig_ism; mod provider; mod rpc; mod trait_builder; +mod transaction; +mod utils; mod validator_announce; diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 762e49d403..b79a803fce 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -20,6 +20,7 @@ use hyperlane_sealevel_mailbox::{ DispatchedMessageAccount, InboxAccount, OutboxAccount, ProcessedMessage, ProcessedMessageAccount, }, + instruction, instruction::InboxProcess, mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, mailbox_process_authority_pda_seeds, mailbox_processed_message_pda_seeds, @@ -52,11 +53,14 @@ use solana_sdk::{ }; use solana_transaction_status::{ EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, TransactionStatus, - UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, UiReturnDataEncoding, - UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, + UiCompiledInstruction, UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, + UiReturnDataEncoding, UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, }; use tracing::{debug, info, instrument, warn}; +use crate::error::HyperlaneSealevelError; +use crate::transaction::search_transaction; +use crate::utils::{decode_h256, decode_h512, from_base58}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; @@ -671,6 +675,8 @@ impl SealevelMailboxIndexer { self.dispatched_message_account(&account) })?; + info!(?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() @@ -683,6 +689,44 @@ impl SealevelMailboxIndexer { let hyperlane_message = HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?; + // TODO + // 1. Request block using dispatched_message_account.slot + // 2. Identify the transaction: + // 2.1 transaction contains Mailbox program in list of accounts (number 15) mailbox.program_id + // 2.2 transaction contains dispatched message PDA in the list of accounts (number 6) valid_message_storage_pda_pubkey + // 2.3 confirm that transaction performs message dispatch (OutboxDispatch) from data + // 3. Extract block hash from block + // 4. Extract transaction signature from transaction (first signature) + + let block = self + .mailbox + .provider + .rpc() + .get_block(dispatched_message_account.slot) + .await?; + let block_hash = decode_h256(&block.blockhash)?; + + let transactions = + block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?; + + let transaction_hashes = search_transaction( + &self.mailbox.program_id, + &valid_message_storage_pda_pubkey, + transactions, + ); + + // We expect to see that there is only one message dispatch transaction + if transaction_hashes.len() > 1 { + Err(HyperlaneSealevelError::TooManyTransactions("Block contains more than one dispatch message transaction operating on the same dispatch message store PDA".to_owned()))? + } + + let transaction_hash = transaction_hashes + .into_iter() + .next() + .ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any after filtering".to_owned()))?; + + // info!(?block, "confirmed_block"); + Ok(( hyperlane_message.into(), LogMeta { @@ -690,8 +734,8 @@ impl SealevelMailboxIndexer { block_number: dispatched_message_account.slot, // TODO: get these when building out scraper support. // It's inconvenient to get these :| - block_hash: H256::zero(), - transaction_id: H512::zero(), + block_hash, + transaction_id: transaction_hash, transaction_index: 0, log_index: U256::zero(), }, diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 932a7191ea..8df1a4baed 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -1,14 +1,13 @@ -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use async_trait::async_trait; use hyperlane_core::{ BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, TxnInfo, H256, H512, U256, }; -use solana_sdk::bs58; -use solana_sdk::pubkey::Pubkey; -use crate::{error::HyperlaneSealevelError, ConnectionConf, SealevelRpcClient}; +use crate::utils::{decode_h256, decode_pubkey}; +use crate::{ConnectionConf, SealevelRpcClient}; /// A wrapper around a Sealevel provider to get generic blockchain information. #[derive(Debug)] @@ -50,10 +49,7 @@ impl HyperlaneProvider for SealevelProvider { async fn get_block_by_height(&self, slot: u64) -> ChainResult { let confirmed_block = self.rpc_client.get_block(slot).await?; - let hash_binary = bs58::decode(confirmed_block.blockhash) - .into_vec() - .map_err(HyperlaneSealevelError::Decoding)?; - let block_hash = H256::from_slice(&hash_binary); + let block_hash = decode_h256(&confirmed_block.blockhash)?; let block_time = confirmed_block .block_time @@ -78,7 +74,7 @@ impl HyperlaneProvider for SealevelProvider { } async fn get_balance(&self, address: String) -> ChainResult { - let pubkey = Pubkey::from_str(&address).map_err(Into::::into)?; + let pubkey = decode_pubkey(&address)?; self.rpc_client.get_balance(&pubkey).await } diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs new file mode 100644 index 0000000000..c0f7607bb8 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -0,0 +1,98 @@ +use std::collections::HashMap; + +use solana_sdk::pubkey::Pubkey; +use solana_transaction_status::{EncodedTransaction, EncodedTransactionWithStatusMeta, UiMessage}; + +use hyperlane_core::H512; +use hyperlane_sealevel_mailbox::instruction::Instruction; + +use crate::utils::{decode_h512, decode_pubkey, from_base58}; + +pub fn search_transaction( + mailbox_program_id: &Pubkey, + message_storage_pda_pubkey: &Pubkey, + transactions: Vec, +) -> Vec { + transactions + .into_iter() + .filter_map(|tx| match tx.transaction { + // We support only transactions encoded as JSON initially + EncodedTransaction::Json(t) => Some(t), + _ => None, + }) + .filter_map(|t| { + let transaction_hash = t.signatures.first().unwrap().to_owned(); + let transaction_hash = match decode_h512(&transaction_hash) { + Ok(h) => h, + Err(_) => return None, // if we cannot parse transaction hash, we continue the search + }; + + // We support only Raw messages initially + match t.message { + UiMessage::Raw(m) => Some((transaction_hash, m)), + _ => None, + } + }) + .filter_map(|(hash, message)| { + let account_keys = message + .account_keys + .into_iter() + .enumerate() + .filter_map(|(index, key)| { + let pubkey = match decode_pubkey(&key) { + Ok(p) => p, + Err(_) => return None, + }; + Some((pubkey, index)) + }) + .collect::>(); + + let mailbox_program_index = match account_keys.get(mailbox_program_id) { + Some(i) => *i as u8, + None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch. + }; + + let dispatch_message_pda_account_index = + match account_keys.get(message_storage_pda_pubkey) { + Some(i) => *i as u8, + None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch. + }; + + let mailbox_program_maybe = message + .instructions + .into_iter() + .filter(|instruction| instruction.program_id_index == mailbox_program_index) + .next(); + + let mailbox_program = match mailbox_program_maybe { + Some(p) => p, + None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch. + }; + + // If Mailbox program should operate on dispatch message store PDA account, transaction is not message dispatch. + if !mailbox_program + .accounts + .contains(&dispatch_message_pda_account_index) + { + return None; + } + + let instruction_data = match from_base58(&mailbox_program.data) { + Ok(d) => d, + Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch. + }; + + let instruction = match Instruction::from_instruction_data(&instruction_data) { + Ok(m) => m, + Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch. + }; + + // If the call into Mailbox program is not OutboxDispatch, transaction is not message dispatch. + if !matches!(instruction, Instruction::OutboxDispatch(_)) { + return None; + } + + Some(hash) + }) + .collect::>() +} diff --git a/rust/main/chains/hyperlane-sealevel/src/utils.rs b/rust/main/chains/hyperlane-sealevel/src/utils.rs new file mode 100644 index 0000000000..56b8202c61 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/utils.rs @@ -0,0 +1,33 @@ +use std::str::FromStr; + +use solana_sdk::bs58; +use solana_sdk::pubkey::Pubkey; + +use hyperlane_core::{H256, H512}; + +use crate::error::HyperlaneSealevelError; + +pub fn from_base58(base58: &str) -> Result, HyperlaneSealevelError> { + let binary = bs58::decode(base58) + .into_vec() + .map_err(HyperlaneSealevelError::Decoding)?; + Ok(binary) +} + +pub fn decode_h256(base58: &str) -> Result { + let binary = from_base58(base58)?; + let hash = H256::from_slice(&binary); + + Ok(hash) +} + +pub fn decode_h512(base58: &str) -> Result { + let binary = from_base58(base58)?; + let hash = H512::from_slice(&binary); + + Ok(hash) +} + +pub fn decode_pubkey(address: &str) -> Result { + Pubkey::from_str(address).map_err(Into::::into) +} From 3efe775cb67f9599dc127f84fb4c47f817109130 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:28:25 +0000 Subject: [PATCH 05/26] Add transaction search in block --- .../chains/hyperlane-sealevel/src/mailbox.rs | 4 +- .../hyperlane-sealevel/src/transaction.rs | 74 ++-- .../src/transaction/tests.rs | 329 ++++++++++++++++++ 3 files changed, 380 insertions(+), 27 deletions(-) create mode 100644 rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index b79a803fce..86abb63821 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -706,6 +706,8 @@ impl SealevelMailboxIndexer { .await?; let block_hash = decode_h256(&block.blockhash)?; + info!(?block_hash, slot = ?dispatched_message_account.slot, "block with dispatch message transaction"); + let transactions = block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?; @@ -842,7 +844,7 @@ impl SealevelMailboxIndexer { nonce: u32, offset: usize, length: usize, - ) -> Result, ChainCommunicationError> { + ) -> ChainResult> { let target_message_account_bytes = &[&discriminator[..], &nonce.to_le_bytes()[..]].concat(); let target_message_account_bytes = base64::encode(target_message_account_bytes); diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index c0f7607bb8..b1e906351b 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -1,12 +1,16 @@ use std::collections::HashMap; use solana_sdk::pubkey::Pubkey; -use solana_transaction_status::{EncodedTransaction, EncodedTransactionWithStatusMeta, UiMessage}; +use solana_transaction_status::option_serializer::OptionSerializer; +use solana_transaction_status::{ + EncodedTransaction, EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction, + UiMessage, +}; use hyperlane_core::H512; use hyperlane_sealevel_mailbox::instruction::Instruction; -use crate::utils::{decode_h512, decode_pubkey, from_base58}; +use crate::utils::{decode_h512, from_base58}; pub fn search_transaction( mailbox_program_id: &Pubkey, @@ -15,51 +19,66 @@ pub fn search_transaction( ) -> Vec { transactions .into_iter() - .filter_map(|tx| match tx.transaction { - // We support only transactions encoded as JSON initially - EncodedTransaction::Json(t) => Some(t), + .filter_map(|tx| match (tx.transaction, tx.meta) { + // We support only transactions encoded as JSON + // We need none-empty metadata as well + (EncodedTransaction::Json(t), Some(m)) => Some((t, m)), _ => None, }) - .filter_map(|t| { - let transaction_hash = t.signatures.first().unwrap().to_owned(); + .filter_map(|(t, m)| { + let transaction_hash = match t.signatures.first() { + Some(h) => h, + None => return None, // if transaction is not signed, we continue the search + }; + let transaction_hash = match decode_h512(&transaction_hash) { Ok(h) => h, Err(_) => return None, // if we cannot parse transaction hash, we continue the search }; // We support only Raw messages initially - match t.message { - UiMessage::Raw(m) => Some((transaction_hash, m)), - _ => None, - } + let message = match t.message { + UiMessage::Raw(m) => m, + _ => return None, + }; + + let instructions = match m.inner_instructions { + OptionSerializer::Some(ii) => ii + .into_iter() + .map(|iii| iii.instructions) + .flatten() + .flat_map(|ii| match ii { + UiInstruction::Compiled(ci) => Some(ci), + _ => None, + }) + .collect::>(), + OptionSerializer::None | OptionSerializer::Skip => return None, + }; + + Some((transaction_hash, message, instructions)) }) - .filter_map(|(hash, message)| { + .filter_map(|(hash, message, instructions)| { let account_keys = message .account_keys .into_iter() .enumerate() - .filter_map(|(index, key)| { - let pubkey = match decode_pubkey(&key) { - Ok(p) => p, - Err(_) => return None, - }; - Some((pubkey, index)) - }) - .collect::>(); - - let mailbox_program_index = match account_keys.get(mailbox_program_id) { + .map(|(index, key)| (key, index)) + .collect::>(); + + let mailbox_program_id_str = mailbox_program_id.to_string(); + let mailbox_program_index = match account_keys.get(&mailbox_program_id_str) { Some(i) => *i as u8, None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch. }; + let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string(); let dispatch_message_pda_account_index = - match account_keys.get(message_storage_pda_pubkey) { + match account_keys.get(&message_storage_pda_pubkey_str) { Some(i) => *i as u8, None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch. }; - let mailbox_program_maybe = message - .instructions + let mailbox_program_maybe = instructions .into_iter() .filter(|instruction| instruction.program_id_index == mailbox_program_index) .next(); @@ -69,7 +88,7 @@ pub fn search_transaction( None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch. }; - // If Mailbox program should operate on dispatch message store PDA account, transaction is not message dispatch. + // If Mailbox program does not operate on dispatch message store PDA account, transaction is not message dispatch. if !mailbox_program .accounts .contains(&dispatch_message_pda_account_index) @@ -96,3 +115,6 @@ pub fn search_transaction( }) .collect::>() } + +#[cfg(test)] +mod tests; diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs new file mode 100644 index 0000000000..1d68cf57b0 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs @@ -0,0 +1,329 @@ +use solana_transaction_status::EncodedTransactionWithStatusMeta; + +use crate::transaction::search_transaction; +use crate::utils::decode_pubkey; + +#[test] +pub fn test() { + // given + let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap(); + let dispatched_message_pda_account = + decode_pubkey("6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT").unwrap(); + let transaction = serde_json::from_str::(JSON).unwrap(); + let transactions = vec![transaction]; + + // when + let transaction_hashes = search_transaction( + &mailbox_program_id, + &dispatched_message_pda_account, + transactions, + ); + + // then + assert!(!transaction_hashes.is_empty()); +} + +const JSON: &str = r#" +{ + "blockTime": 1729865514, + "meta": { + "computeUnitsConsumed": 171834, + "err": null, + "fee": 3564950, + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "accounts": [ + 8, + 7, + 6, + 0 + ], + "data": "gCzo5F74HA9Pb", + "programIdIndex": 19, + "stackHeight": 2 + }, + { + "accounts": [ + 5, + 11, + 10, + 18, + 0, + 1, + 2 + ], + "data": "2Nsbnwq8JuYnSefHfRznxFtFqdPnbeydtt5kenfF8GR1ZU2XtF8jJDo4SUc2VY52V5C25WsKsQZBLsoCVQNzefgVj2bVznkThjuZuSKXJfZN9ADggiM2soRKVsAjf3xHm3CC3w3iyvK5U9LsjmYtiDNbJCFtEPRTDxsfvMS45Bg3q6EogmBN9JiZNLP", + "programIdIndex": 17, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 5 + ], + "data": "3Bxs3zrfFUZbEPqZ", + "programIdIndex": 10, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 2 + ], + "data": "11114XfZCGKrze4PNou1GXiYCJgiBCGpHks9hxjb8tFwYMjtgVtMzvriDxwYPdRqSoqztL", + "programIdIndex": 10, + "stackHeight": 3 + }, + { + "accounts": [ + 10, + 0, + 3, + 1, + 4, + 9, + 14 + ], + "data": "5MtKiLZhPB3NhS7Gus6CenAEMS2QBtpY9QtuLeVH4CkpUN7599vsYzZXhk8Vu", + "programIdIndex": 15, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 9 + ], + "data": "3Bxs4A3YxXXYy5gj", + "programIdIndex": 10, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 4 + ], + "data": "111158VjdPaAaGVkCbPZoXJqknHXBEqoypfVjf96mwePbKxAkrKfR2gUFyN7wD8ccc9g1z", + "programIdIndex": 10, + "stackHeight": 3 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm invoke [1]", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: TransferChecked", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6200 of 983051 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Protocol fee of 0 paid from FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md to BvZpTuYLAR77mPhH4GtvwEWUTs53GQqkgBNuXpCePVNk", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Dispatched message to 1408864445, ID 0x09c74f3e10d98c112696b72ba1609aae47616f64f28b4cb1ad8a4a710e93ee89", + "Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi consumed 86420 of 972001 compute units", + "Program return: E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi CcdPPhDZjBEmlrcroWCarkdhb2Tyi0yxrYpKcQ6T7ok=", + "Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi success", + "Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Paid IGP JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M for 431000 gas for message 0x09c7…ee89 to 1408864445", + "Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv consumed 42792 of 882552 compute units", + "Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv success", + "Program log: Warp route transfer completed to destination: 1408864445, recipient: 0xd41b…f050, remote_amount: 2206478600", + "Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm consumed 171534 of 999700 compute units", + "Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm success" + ], + "postBalances": [ + 12374928, + 0, + 2241120, + 1016160, + 1872240, + 8679120, + 2039280, + 319231603414, + 2039280, + 10172586528, + 1, + 890880, + 1141440, + 3361680, + 1830480, + 1141440, + 1, + 1141440, + 1141440, + 934087680 + ], + "postTokenBalances": [ + { + "accountIndex": 6, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "165697511204", + "decimals": 6, + "uiAmount": 165697.511204, + "uiAmountString": "165697.511204" + } + }, + { + "accountIndex": 8, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "94", + "decimals": 6, + "uiAmount": 9.4E-5, + "uiAmountString": "0.000094" + } + } + ], + "preBalances": [ + 22211372, + 0, + 0, + 1016160, + 0, + 8679120, + 2039280, + 319231603414, + 2039280, + 10170428394, + 1, + 890880, + 1141440, + 3361680, + 1830480, + 1141440, + 1, + 1141440, + 1141440, + 934087680 + ], + "preTokenBalances": [ + { + "accountIndex": 6, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "163491032604", + "decimals": 6, + "uiAmount": 163491.032604, + "uiAmountString": "163491.032604" + } + }, + { + "accountIndex": 8, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "2206478694", + "decimals": 6, + "uiAmount": 2206.478694, + "uiAmountString": "2206.478694" + } + } + ], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 297626301, + "transaction": { + "message": { + "accountKeys": [ + "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md", + "8DqWVhEZcg4rDYwe5UFaopmGuEajiPz9L3A1ZnytMcUm", + "6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT", + "8Cv4PHJ6Cf3xY7dse7wYeZKtuQv9SAN6ujt5w22a2uho", + "9yMwrDqHsbmmvYPS9h4MLPbe2biEykcL51W7qJSDL5hF", + "BvZpTuYLAR77mPhH4GtvwEWUTs53GQqkgBNuXpCePVNk", + "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "FDDbaNtod9pt7pmR8qtmRZJtEj9NViDA7J6cazqUjXQj", + "JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M", + "11111111111111111111111111111111", + "37N3sbyVAd3KvQsPw42i1LWkLahzL4ninVQ4n1NmnHjS", + "3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm", + "AHX3iiEPFMyygANrp15cyUr63o9qGkwkB6ki1pgpZ7gZ", + "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF", + "BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv", + "ComputeBudget111111111111111111111111111111", + "E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ], + "header": { + "numReadonlySignedAccounts": 1, + "numReadonlyUnsignedAccounts": 10, + "numRequiredSignatures": 2 + }, + "instructions": [ + { + "accounts": [], + "data": "FjL4FH", + "programIdIndex": 16, + "stackHeight": null + }, + { + "accounts": [], + "data": "3butUEijJrLf", + "programIdIndex": 16, + "stackHeight": null + }, + { + "accounts": [ + 10, + 18, + 13, + 17, + 5, + 11, + 0, + 1, + 2, + 15, + 3, + 4, + 14, + 9, + 19, + 7, + 8, + 6 + ], + "data": "RpjV6TtUSvt6UnMXdNo4h1Ze2VGVifo65r2jqRBUq6HJKhskSnwWybXyB4NxgfvedV9vhKdmDPg8sFT64JEZvxF8VfoGdqoAFt4WFLSB", + "programIdIndex": 12, + "stackHeight": null + } + ], + "recentBlockhash": "GHQhVUy7Eq3hcps8YoG9DCd1Tb6ccQZ9xhh81ju8ujHJ" + }, + "signatures": [ + "4nRGgV9tqCuiKUXeBzWdvdk6YC9BsGWUZurAVQLMX1NwNPpysbZNwXu97Sw4aM9REwaRmWS7gaiSKXbwtmw6oLRi", + "hXjvQbAuFH9vAxZMdGqfnSjN7t7Z7NLTzRq1SG8i6fLr9LS6XahTduPWqakiTsLDyWSofvq3MSncUAkbQLEj85f" + ] + } +} +"#; From 5f3ea6e95e091a01e14024735bc1ee49cb325f9f Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:42:43 +0000 Subject: [PATCH 06/26] Improve documentation of transaction search function --- .../chains/hyperlane-sealevel/src/mailbox.rs | 9 --------- .../chains/hyperlane-sealevel/src/transaction.rs | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 86abb63821..834348f983 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -689,15 +689,6 @@ impl SealevelMailboxIndexer { let hyperlane_message = HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?; - // TODO - // 1. Request block using dispatched_message_account.slot - // 2. Identify the transaction: - // 2.1 transaction contains Mailbox program in list of accounts (number 15) mailbox.program_id - // 2.2 transaction contains dispatched message PDA in the list of accounts (number 6) valid_message_storage_pda_pubkey - // 2.3 confirm that transaction performs message dispatch (OutboxDispatch) from data - // 3. Extract block hash from block - // 4. Extract transaction signature from transaction (first signature) - let block = self .mailbox .provider diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index b1e906351b..b658f3d154 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -12,6 +12,22 @@ use hyperlane_sealevel_mailbox::instruction::Instruction; use crate::utils::{decode_h512, from_base58}; +/// This function searches for a transaction which dispatches Hyperlane message and returns +/// list of hashes of such transactions. +/// +/// This function takes the mailbox program identifier and the identifier for PDA for storing +/// a dispatched message and searches a message dispatch transaction in a list of transaction. +/// The list of transaction is usually comes from a block. The function returns list of hashes +/// of such transactions. +/// +/// The transaction will be searched with the following criteria: +/// 1. Transaction contains Mailbox program id in the list of accounts. +/// 2. Transaction contains dispatched message PDA in the list of accounts. +/// 3. Transaction is performing message dispatch (OutboxDispatch). +/// +/// * `mailbox_program_id` - Identifier of Mailbox program +/// * `message_storage_pda_pubkey` - Identifier for dispatch message store PDA +/// * `transactions` - List of transactions pub fn search_transaction( mailbox_program_id: &Pubkey, message_storage_pda_pubkey: &Pubkey, From 703483f6eaf33b036b567db6a9d7810db157534f Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:30:48 +0000 Subject: [PATCH 07/26] Add initial transaction request from Solana --- .../chains/hyperlane-sealevel/src/error.rs | 12 ++++- .../chains/hyperlane-sealevel/src/provider.rs | 53 +++++++++++++++++-- .../hyperlane-sealevel/src/rpc/client.rs | 39 ++++++++++---- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/error.rs b/rust/main/chains/hyperlane-sealevel/src/error.rs index 00d2ac2e03..c831631d25 100644 --- a/rust/main/chains/hyperlane-sealevel/src/error.rs +++ b/rust/main/chains/hyperlane-sealevel/src/error.rs @@ -1,6 +1,7 @@ -use hyperlane_core::ChainCommunicationError; +use hyperlane_core::{ChainCommunicationError, H512}; use solana_client::client_error::ClientError; use solana_sdk::pubkey::ParsePubkeyError; +use solana_transaction_status::EncodedTransaction; /// Errors from the crates specific to the hyperlane-sealevel /// implementation. @@ -23,6 +24,15 @@ pub enum HyperlaneSealevelError { /// Too many transactions of particular content in block #[error("{0}")] TooManyTransactions(String), + /// Unsupported transaction encoding + #[error("{0:?}")] + UnsupportedTransactionEncoding(EncodedTransaction), + /// Unsigned transaction + #[error("{0}")] + UnsignedTransaction(H512), + /// Incorrect transaction + #[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")] + IncorrectTransaction(H512, H512), } impl From for ChainCommunicationError { diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 8df1a4baed..2b07bff1ec 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -1,12 +1,16 @@ use std::sync::Arc; use async_trait::async_trait; +use solana_sdk::signature::Signature; +use solana_transaction_status::EncodedTransaction; + use hyperlane_core::{ - BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, - HyperlaneProviderError, TxnInfo, H256, H512, U256, + BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, + HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256, }; -use crate::utils::{decode_h256, decode_pubkey}; +use crate::error::HyperlaneSealevelError; +use crate::utils::{decode_h256, decode_h512, decode_pubkey}; use crate::{ConnectionConf, SealevelRpcClient}; /// A wrapper around a Sealevel provider to get generic blockchain information. @@ -64,8 +68,47 @@ impl HyperlaneProvider for SealevelProvider { Ok(block_info) } - async fn get_txn_by_hash(&self, _hash: &H512) -> ChainResult { - todo!() // FIXME + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + let signature = Signature::new(hash.as_bytes()); + let transaction = self.rpc_client.get_transaction(&signature).await?; + + let ui_transaction = match transaction.transaction.transaction { + EncodedTransaction::Json(t) => t, + t => Err(HyperlaneSealevelError::UnsupportedTransactionEncoding(t)) + .map_err(Into::::into)?, + }; + + let received_signature = ui_transaction + .signatures + .first() + .ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?; + let received_hash = decode_h512(received_signature)?; + + if &received_hash != hash { + Err(HyperlaneSealevelError::IncorrectTransaction( + *hash, + received_hash, + )) + .map_err(Into::::into)?; + } + + let receipt = TxnReceiptInfo { + gas_used: Default::default(), + cumulative_gas_used: Default::default(), + effective_gas_price: None, + }; + + Ok(TxnInfo { + hash: *hash, + gas_limit: Default::default(), + max_priority_fee_per_gas: None, + max_fee_per_gas: None, + gas_price: None, + nonce: 0, + sender: Default::default(), + recipient: None, + receipt: Some(receipt), + }) } async fn is_contract(&self, _address: &H256) -> ChainResult { diff --git a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs index 44ba2c8d59..88a474cbbf 100644 --- a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs +++ b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs @@ -1,10 +1,9 @@ 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::RpcBlockConfig, - rpc_config::RpcProgramAccountsConfig, rpc_response::Response, + rpc_config::RpcProgramAccountsConfig, rpc_config::RpcTransactionConfig, rpc_response::Response, }; use solana_sdk::{ account::Account, @@ -17,9 +16,12 @@ use solana_sdk::{ transaction::Transaction, }; use solana_transaction_status::{ - TransactionStatus, UiConfirmedBlock, UiReturnDataEncoding, UiTransactionReturnData, + EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock, + UiReturnDataEncoding, UiTransactionReturnData, }; +use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; + use crate::error::HyperlaneSealevelError; pub struct SealevelRpcClient(RpcClient); @@ -99,6 +101,17 @@ impl SealevelRpcClient { Ok(account) } + 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 get_block(&self, height: u64) -> ChainResult { let config = RpcBlockConfig { commitment: Some(CommitmentConfig::finalized()), @@ -170,15 +183,19 @@ impl SealevelRpcClient { .map_err(ChainCommunicationError::from_other) } - pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult { - let balance = self - .0 - .get_balance(pubkey) + pub async fn get_transaction( + &self, + signature: &Signature, + ) -> ChainResult { + let config = RpcTransactionConfig { + commitment: Some(CommitmentConfig::finalized()), + ..Default::default() + }; + self.0 + .get_transaction_with_config(signature, config) .await - .map_err(Into::::into) - .map_err(ChainCommunicationError::from)?; - - Ok(balance.into()) + .map_err(HyperlaneSealevelError::ClientError) + .map_err(Into::into) } pub async fn is_blockhash_valid(&self, hash: &Hash) -> ChainResult { From 0801d86b358b8fdf91aee8a41dc2e029b3e2ffbf Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:33:41 +0000 Subject: [PATCH 08/26] Rename method --- rust/main/chains/hyperlane-sealevel/src/mailbox.rs | 4 ++-- rust/main/chains/hyperlane-sealevel/src/transaction.rs | 2 +- rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 834348f983..cd45c083a2 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -59,7 +59,7 @@ use solana_transaction_status::{ use tracing::{debug, info, instrument, warn}; use crate::error::HyperlaneSealevelError; -use crate::transaction::search_transaction; +use crate::transaction::search_transactions; use crate::utils::{decode_h256, decode_h512, from_base58}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; @@ -702,7 +702,7 @@ impl SealevelMailboxIndexer { let transactions = block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?; - let transaction_hashes = search_transaction( + let transaction_hashes = search_transactions( &self.mailbox.program_id, &valid_message_storage_pda_pubkey, transactions, diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index b658f3d154..7e8bd07fca 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -28,7 +28,7 @@ use crate::utils::{decode_h512, from_base58}; /// * `mailbox_program_id` - Identifier of Mailbox program /// * `message_storage_pda_pubkey` - Identifier for dispatch message store PDA /// * `transactions` - List of transactions -pub fn search_transaction( +pub fn search_transactions( mailbox_program_id: &Pubkey, message_storage_pda_pubkey: &Pubkey, transactions: Vec, diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs index 1d68cf57b0..d82e60c755 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs @@ -1,6 +1,6 @@ use solana_transaction_status::EncodedTransactionWithStatusMeta; -use crate::transaction::search_transaction; +use crate::transaction::search_transactions; use crate::utils::decode_pubkey; #[test] @@ -13,7 +13,7 @@ pub fn test() { let transactions = vec![transaction]; // when - let transaction_hashes = search_transaction( + let transaction_hashes = search_transactions( &mailbox_program_id, &dispatched_message_pda_account, transactions, From ca16f9cb3fa025e81cb584fd5a55d364a6fe6af7 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:35:58 +0000 Subject: [PATCH 09/26] Remove unnecessary logging --- rust/main/chains/hyperlane-sealevel/src/mailbox.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index cd45c083a2..2b6bb1aaaf 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -675,8 +675,6 @@ impl SealevelMailboxIndexer { self.dispatched_message_account(&account) })?; - info!(?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() @@ -697,8 +695,6 @@ impl SealevelMailboxIndexer { .await?; let block_hash = decode_h256(&block.blockhash)?; - info!(?block_hash, slot = ?dispatched_message_account.slot, "block with dispatch message transaction"); - let transactions = block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?; From f4a536268f6954c34ea8c829a9b4bbf06bfa4b96 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:09:04 +0000 Subject: [PATCH 10/26] Fix clippy feedback --- rust/main/chains/hyperlane-sealevel/src/error.rs | 2 +- .../chains/hyperlane-sealevel/src/provider.rs | 16 +++++++++------- .../chains/hyperlane-sealevel/src/transaction.rs | 8 +++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/error.rs b/rust/main/chains/hyperlane-sealevel/src/error.rs index c831631d25..4ba2171191 100644 --- a/rust/main/chains/hyperlane-sealevel/src/error.rs +++ b/rust/main/chains/hyperlane-sealevel/src/error.rs @@ -32,7 +32,7 @@ pub enum HyperlaneSealevelError { UnsignedTransaction(H512), /// Incorrect transaction #[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")] - IncorrectTransaction(H512, H512), + IncorrectTransaction(Box, Box), } impl From for ChainCommunicationError { diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 2b07bff1ec..ba34cb69c6 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -74,8 +74,9 @@ impl HyperlaneProvider for SealevelProvider { let ui_transaction = match transaction.transaction.transaction { EncodedTransaction::Json(t) => t, - t => Err(HyperlaneSealevelError::UnsupportedTransactionEncoding(t)) - .map_err(Into::::into)?, + t => Err(Into::::into( + HyperlaneSealevelError::UnsupportedTransactionEncoding(t), + ))?, }; let received_signature = ui_transaction @@ -85,11 +86,12 @@ impl HyperlaneProvider for SealevelProvider { let received_hash = decode_h512(received_signature)?; if &received_hash != hash { - Err(HyperlaneSealevelError::IncorrectTransaction( - *hash, - received_hash, - )) - .map_err(Into::::into)?; + Err(Into::::into( + HyperlaneSealevelError::IncorrectTransaction( + Box::new(*hash), + Box::new(received_hash), + ), + ))?; } let receipt = TxnReceiptInfo { diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 7e8bd07fca..44fdb4c900 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -47,7 +47,7 @@ pub fn search_transactions( None => return None, // if transaction is not signed, we continue the search }; - let transaction_hash = match decode_h512(&transaction_hash) { + let transaction_hash = match decode_h512(transaction_hash) { Ok(h) => h, Err(_) => return None, // if we cannot parse transaction hash, we continue the search }; @@ -61,8 +61,7 @@ pub fn search_transactions( let instructions = match m.inner_instructions { OptionSerializer::Some(ii) => ii .into_iter() - .map(|iii| iii.instructions) - .flatten() + .flat_map(|iii| iii.instructions) .flat_map(|ii| match ii { UiInstruction::Compiled(ci) => Some(ci), _ => None, @@ -96,8 +95,7 @@ pub fn search_transactions( let mailbox_program_maybe = instructions .into_iter() - .filter(|instruction| instruction.program_id_index == mailbox_program_index) - .next(); + .find(|instruction| instruction.program_id_index == mailbox_program_index); let mailbox_program = match mailbox_program_maybe { Some(p) => p, From c1dcb8dbedb41f8cbe29335b3b568d4e776ab14f Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:26:42 +0000 Subject: [PATCH 11/26] Remove commented out code --- rust/main/chains/hyperlane-sealevel/src/mailbox.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 2b6bb1aaaf..ca355ffd83 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -714,8 +714,6 @@ impl SealevelMailboxIndexer { .next() .ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any after filtering".to_owned()))?; - // info!(?block, "confirmed_block"); - Ok(( hyperlane_message.into(), LogMeta { From fe375f080188a84a1c6cfa008efd2118b35d57ec Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:40:51 +0000 Subject: [PATCH 12/26] Rename method --- rust/main/chains/hyperlane-sealevel/src/mailbox.rs | 4 ++-- rust/main/chains/hyperlane-sealevel/src/transaction.rs | 2 +- rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index ca355ffd83..b58f369a20 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -59,7 +59,7 @@ use solana_transaction_status::{ use tracing::{debug, info, instrument, warn}; use crate::error::HyperlaneSealevelError; -use crate::transaction::search_transactions; +use crate::transaction::search_dispatched_message_transactions; use crate::utils::{decode_h256, decode_h512, from_base58}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; @@ -698,7 +698,7 @@ impl SealevelMailboxIndexer { let transactions = block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?; - let transaction_hashes = search_transactions( + let transaction_hashes = search_dispatched_message_transactions( &self.mailbox.program_id, &valid_message_storage_pda_pubkey, transactions, diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 44fdb4c900..274ec82eb2 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -28,7 +28,7 @@ use crate::utils::{decode_h512, from_base58}; /// * `mailbox_program_id` - Identifier of Mailbox program /// * `message_storage_pda_pubkey` - Identifier for dispatch message store PDA /// * `transactions` - List of transactions -pub fn search_transactions( +pub fn search_dispatched_message_transactions( mailbox_program_id: &Pubkey, message_storage_pda_pubkey: &Pubkey, transactions: Vec, diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs index d82e60c755..deb20f1ae7 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs @@ -1,6 +1,6 @@ use solana_transaction_status::EncodedTransactionWithStatusMeta; -use crate::transaction::search_transactions; +use crate::transaction::search_dispatched_message_transactions; use crate::utils::decode_pubkey; #[test] @@ -13,7 +13,7 @@ pub fn test() { let transactions = vec![transaction]; // when - let transaction_hashes = search_transactions( + let transaction_hashes = search_dispatched_message_transactions( &mailbox_program_id, &dispatched_message_pda_account, transactions, From 7eb54f81e4b5b144fc622fcf560210295cf488b9 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:44:28 +0000 Subject: [PATCH 13/26] Populate transaction index in LogMeta --- .../chains/hyperlane-sealevel/src/mailbox.rs | 4 ++-- .../hyperlane-sealevel/src/transaction.rs | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index b58f369a20..c44115579f 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -709,7 +709,7 @@ impl SealevelMailboxIndexer { Err(HyperlaneSealevelError::TooManyTransactions("Block contains more than one dispatch message transaction operating on the same dispatch message store PDA".to_owned()))? } - let transaction_hash = transaction_hashes + let (transaction_index, transaction_hash) = transaction_hashes .into_iter() .next() .ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any after filtering".to_owned()))?; @@ -723,7 +723,7 @@ impl SealevelMailboxIndexer { // It's inconvenient to get these :| block_hash, transaction_id: transaction_hash, - transaction_index: 0, + transaction_index: transaction_index as u64, log_index: U256::zero(), }, )) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 274ec82eb2..0a9526a6c4 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -32,16 +32,17 @@ pub fn search_dispatched_message_transactions( mailbox_program_id: &Pubkey, message_storage_pda_pubkey: &Pubkey, transactions: Vec, -) -> Vec { +) -> Vec<(usize, H512)> { transactions .into_iter() - .filter_map(|tx| match (tx.transaction, tx.meta) { + .enumerate() + .filter_map(|(index, tx)| match (tx.transaction, tx.meta) { // We support only transactions encoded as JSON // We need none-empty metadata as well - (EncodedTransaction::Json(t), Some(m)) => Some((t, m)), + (EncodedTransaction::Json(t), Some(m)) => Some((index, t, m)), _ => None, }) - .filter_map(|(t, m)| { + .filter_map(|(index, t, m)| { let transaction_hash = match t.signatures.first() { Some(h) => h, None => return None, // if transaction is not signed, we continue the search @@ -70,9 +71,9 @@ pub fn search_dispatched_message_transactions( OptionSerializer::None | OptionSerializer::Skip => return None, }; - Some((transaction_hash, message, instructions)) + Some((index, transaction_hash, message, instructions)) }) - .filter_map(|(hash, message, instructions)| { + .filter_map(|(index, hash, message, instructions)| { let account_keys = message .account_keys .into_iter() @@ -125,9 +126,9 @@ pub fn search_dispatched_message_transactions( return None; } - Some(hash) + Some((index, hash)) }) - .collect::>() + .collect::>() } #[cfg(test)] From 3987c73cbbeb3677b5669551cf0e76654e2d8914 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:46:03 +0000 Subject: [PATCH 14/26] Rename test method --- rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs index deb20f1ae7..759f15de97 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs @@ -4,7 +4,7 @@ use crate::transaction::search_dispatched_message_transactions; use crate::utils::decode_pubkey; #[test] -pub fn test() { +pub fn test_search_dispatched_message_transaction() { // given let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap(); let dispatched_message_pda_account = From e2785b97433abc1ff4e98a91326b0270cdd2ffde Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:12:50 +0000 Subject: [PATCH 15/26] Rename --- rust/main/chains/hyperlane-sealevel/src/transaction.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 0a9526a6c4..6f620c5620 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -42,8 +42,8 @@ pub fn search_dispatched_message_transactions( (EncodedTransaction::Json(t), Some(m)) => Some((index, t, m)), _ => None, }) - .filter_map(|(index, t, m)| { - let transaction_hash = match t.signatures.first() { + .filter_map(|(index, tx, meta)| { + let transaction_hash = match tx.signatures.first() { Some(h) => h, None => return None, // if transaction is not signed, we continue the search }; @@ -54,15 +54,15 @@ pub fn search_dispatched_message_transactions( }; // We support only Raw messages initially - let message = match t.message { + let message = match tx.message { UiMessage::Raw(m) => m, _ => return None, }; - let instructions = match m.inner_instructions { + let instructions = match meta.inner_instructions { OptionSerializer::Some(ii) => ii .into_iter() - .flat_map(|iii| iii.instructions) + .flat_map(|ii| ii.instructions) .flat_map(|ii| match ii { UiInstruction::Compiled(ci) => Some(ci), _ => None, From 85b9d84bddaaaba1f884422ee80b524762186986 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:19:44 +0000 Subject: [PATCH 16/26] Concatenate instructions and inner instructions --- .../main/chains/hyperlane-sealevel/src/transaction.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 6f620c5620..bf1b1e3e2e 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -59,7 +59,7 @@ pub fn search_dispatched_message_transactions( _ => return None, }; - let instructions = match meta.inner_instructions { + let inner_instructions = match meta.inner_instructions { OptionSerializer::Some(ii) => ii .into_iter() .flat_map(|ii| ii.instructions) @@ -71,11 +71,12 @@ pub fn search_dispatched_message_transactions( OptionSerializer::None | OptionSerializer::Skip => return None, }; - Some((index, transaction_hash, message, instructions)) + let instructions = vec![message.instructions, inner_instructions].concat(); + + Some((index, transaction_hash, message.account_keys, instructions)) }) - .filter_map(|(index, hash, message, instructions)| { - let account_keys = message - .account_keys + .filter_map(|(index, hash, account_keys, instructions)| { + let account_keys = account_keys .into_iter() .enumerate() .map(|(index, key)| (key, index)) From 42fb1e6422998fd47ae8080698fcca4d6a8fe81a Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:00:14 +0000 Subject: [PATCH 17/26] Re-use account related functions in interchain gas payment indexing --- .../chains/hyperlane-sealevel/src/account.rs | 72 +++++++++++ .../hyperlane-sealevel/src/interchain_gas.rs | 114 ++++++------------ .../main/chains/hyperlane-sealevel/src/lib.rs | 1 + .../chains/hyperlane-sealevel/src/mailbox.rs | 102 +++++----------- 4 files changed, 139 insertions(+), 150 deletions(-) create mode 100644 rust/main/chains/hyperlane-sealevel/src/account.rs diff --git a/rust/main/chains/hyperlane-sealevel/src/account.rs b/rust/main/chains/hyperlane-sealevel/src/account.rs new file mode 100644 index 0000000000..8fc6d444b0 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/account.rs @@ -0,0 +1,72 @@ +use base64::{engine::general_purpose::STANDARD as Base64, Engine}; +use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; +use solana_client::{ + rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, + rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, +}; +use solana_sdk::{account::Account, commitment_config::CommitmentConfig, pubkey::Pubkey}; + +use hyperlane_core::{ChainCommunicationError, ChainResult}; + +use crate::rpc::SealevelRpcClient; + +pub async fn search_accounts_by_discriminator( + client: &SealevelRpcClient, + program_id: &Pubkey, + discriminator: &[u8; 8], + nonce_bytes: &[u8], + offset: usize, + length: usize, +) -> ChainResult> { + let target_message_account_bytes = &[&discriminator[..], &nonce_bytes[..]].concat(); + let target_message_account_bytes = Base64.encode(target_message_account_bytes); + + // First, find all accounts with the matching account 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 field which was used to generate account id + #[allow(deprecated)] + let memcmp = RpcFilterType::Memcmp(Memcmp { + // Ignore the first byte, which is the `initialized` bool flag. + offset: 1, + bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes), + encoding: None, + }); + let config = RpcProgramAccountsConfig { + filters: Some(vec![memcmp]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: Some(UiDataSliceConfig { offset, length }), + commitment: Some(CommitmentConfig::finalized()), + min_context_slot: None, + }, + with_context: Some(false), + }; + let accounts = client + .get_program_accounts_with_config(program_id, config) + .await?; + Ok(accounts) +} + +pub fn search_and_validate_account( + accounts: Vec<(Pubkey, Account)>, + message_account: F, +) -> ChainResult +where + F: Fn(&Account) -> ChainResult, +{ + let mut valid_storage_pda_pubkey = Option::::None; + + for (pubkey, account) in accounts { + let expected_pubkey = message_account(&account)?; + if expected_pubkey == pubkey { + valid_storage_pda_pubkey = Some(pubkey); + break; + } + } + + let valid_storage_pda_pubkey = valid_storage_pda_pubkey.ok_or_else(|| { + ChainCommunicationError::from_other_str("Could not find valid storage PDA pubkey") + })?; + Ok(valid_storage_pda_pubkey) +} diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index d2f78eb4b4..6436267e27 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -1,25 +1,22 @@ +use std::ops::RangeInclusive; + use async_trait::async_trait; -use hyperlane_core::{ - config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, - HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, - InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, -}; +use derive_new::new; use hyperlane_sealevel_igp::{ accounts::{GasPaymentAccount, ProgramDataAccount}, igp_gas_payment_pda_seeds, igp_program_data_pda_seeds, }; -use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; -use solana_client::{ - rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, - rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, -}; -use std::ops::RangeInclusive; +use solana_sdk::{account::Account, pubkey::Pubkey}; use tracing::{info, instrument}; -use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; +use hyperlane_core::{ + config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, + HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, + InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, +}; -use derive_new::new; +use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; /// The offset to get the `unique_gas_payment_pubkey` field from the serialized GasPaymentData. /// The account data includes prefixes that are accounted for here: a 1 byte initialized flag @@ -121,70 +118,23 @@ impl SealevelInterchainGasPaymasterIndexer { &self, sequence_number: u64, ) -> ChainResult { - let payment_bytes = &[ - &hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR[..], - &sequence_number.to_le_bytes()[..], - ] - .concat(); - #[allow(deprecated)] - 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. - #[allow(deprecated)] - 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: UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET, - length: 32, // the length of the `unique_gas_payment_pubkey` field - }), - commitment: Some(CommitmentConfig::finalized()), - min_context_slot: None, - }, - with_context: Some(false), - }; - tracing::debug!(config=?config, "Fetching program accounts"); - let accounts = self - .rpc_client - .get_program_accounts_with_config(&self.igp.program_id, config) - .await?; + let discriminator = hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR; + let sequence_number_bytes = sequence_number.to_le_bytes(); + let length = 32; // the length of the `unique_gas_payment_pubkey` field + let accounts = search_accounts_by_discriminator( + &self.rpc_client, + &self.igp.program_id, + &discriminator, + &sequence_number_bytes, + UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET, + length, + ) + .await?; tracing::debug!(accounts=?accounts, "Fetched program accounts"); - // Now loop through matching accounts and find the one with a valid account pubkey - // that proves it's an actual gas payment PDA. - let mut valid_payment_pda_pubkey = Option::::None; - - for (pubkey, account) in accounts { - let unique_gas_payment_pubkey = Pubkey::new(&account.data); - let (expected_pubkey, _bump) = Pubkey::try_find_program_address( - igp_gas_payment_pda_seeds!(unique_gas_payment_pubkey), - &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 gas payment PDA pubkey") + let valid_payment_pda_pubkey = search_and_validate_account(accounts, |account| { + self.interchain_payment_account(&account) })?; // Now that we have the valid gas payment PDA pubkey, we can get the full account data. @@ -224,6 +174,20 @@ impl SealevelInterchainGasPaymasterIndexer { H256::from(gas_payment_account.igp.to_bytes()), )) } + + fn interchain_payment_account(&self, account: &Account) -> ChainResult { + let unique_gas_payment_pubkey = Pubkey::new(&account.data); + let (expected_pubkey, _bump) = Pubkey::try_find_program_address( + igp_gas_payment_pda_seeds!(unique_gas_payment_pubkey), + &self.igp.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find program address for unique_gas_payment_pubkey", + ) + })?; + Ok(expected_pubkey) + } } #[async_trait] diff --git a/rust/main/chains/hyperlane-sealevel/src/lib.rs b/rust/main/chains/hyperlane-sealevel/src/lib.rs index f40fd040c9..941c64a7bd 100644 --- a/rust/main/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/main/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 account; mod error; mod interchain_gas; mod interchain_security_module; diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index c44115579f..340152e83b 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -58,6 +58,7 @@ use solana_transaction_status::{ }; use tracing::{debug, info, instrument, warn}; +use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; use crate::error::HyperlaneSealevelError; use crate::transaction::search_dispatched_message_transactions; use crate::utils::{decode_h256, decode_h512, from_base58}; @@ -664,16 +665,22 @@ impl SealevelMailboxIndexer { nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { let discriminator = hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR; + let nonce_bytes = nonce.to_le_bytes(); let offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field let length = 32; // the length of the `unique_message_pubkey` field - let accounts = self - .search_accounts_by_discriminator(&discriminator, nonce, offset, length) - .await?; + let accounts = search_accounts_by_discriminator( + self.rpc(), + &self.program_id, + &discriminator, + &nonce_bytes, + offset, + length, + ) + .await?; - let valid_message_storage_pda_pubkey = self - .search_and_validate_account(accounts, |account| { - self.dispatched_message_account(&account) - })?; + let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| { + self.dispatched_message_account(&account) + })?; // Now that we have the valid message storage PDA pubkey, we can get the full account data. let account = self @@ -748,18 +755,24 @@ impl SealevelMailboxIndexer { nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { let discriminator = hyperlane_sealevel_mailbox::accounts::PROCESSED_MESSAGE_DISCRIMINATOR; + let nonce_bytes = nonce.to_le_bytes(); let offset = 1 + 8 + 8; // the offset to get the `message_id` field let length = 32; - let accounts = self - .search_accounts_by_discriminator(&discriminator, nonce, offset, length) - .await?; + let accounts = search_accounts_by_discriminator( + self.rpc(), + &self.program_id, + &discriminator, + &nonce_bytes, + offset, + length, + ) + .await?; debug!(account_len = ?accounts.len(), "Found accounts with processed message discriminator"); - let valid_message_storage_pda_pubkey = self - .search_and_validate_account(accounts, |account| { - self.delivered_message_account(&account) - })?; + let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| { + self.delivered_message_account(&account) + })?; // Now that we have the valid delivered message storage PDA pubkey, // we can get the full account data. @@ -798,67 +811,6 @@ impl SealevelMailboxIndexer { })?; Ok(expected_pubkey) } - - fn search_and_validate_account( - &self, - accounts: Vec<(Pubkey, Account)>, - message_account: F, - ) -> ChainResult - where - F: Fn(&Account) -> ChainResult, - { - let mut valid_storage_pda_pubkey = Option::::None; - - for (pubkey, account) in accounts { - let expected_pubkey = message_account(&account)?; - if expected_pubkey == pubkey { - valid_storage_pda_pubkey = Some(pubkey); - break; - } - } - - let valid_storage_pda_pubkey = valid_storage_pda_pubkey.ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find valid storage PDA pubkey") - })?; - Ok(valid_storage_pda_pubkey) - } - - async fn search_accounts_by_discriminator( - &self, - discriminator: &[u8; 8], - nonce: u32, - offset: usize, - length: usize, - ) -> ChainResult> { - let target_message_account_bytes = &[&discriminator[..], &nonce.to_le_bytes()[..]].concat(); - let target_message_account_bytes = base64::encode(target_message_account_bytes); - - // First, find all accounts with the matching account 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_message_pubkey` field. - let memcmp = RpcFilterType::Memcmp(Memcmp { - // Ignore the first byte, which is the `initialized` bool flag. - offset: 1, - bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes), - encoding: None, - }); - let config = RpcProgramAccountsConfig { - filters: Some(vec![memcmp]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64), - data_slice: Some(UiDataSliceConfig { offset, length }), - commitment: Some(CommitmentConfig::finalized()), - min_context_slot: None, - }, - with_context: Some(false), - }; - let accounts = self - .rpc() - .get_program_accounts_with_config(&self.mailbox.program_id, config) - .await?; - Ok(accounts) - } } #[async_trait] From 695753730e33720e6e6c232bcc8f496b73c3ef25 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:02:19 +0000 Subject: [PATCH 18/26] Make clippy happy --- rust/main/chains/hyperlane-sealevel/src/account.rs | 2 +- rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs | 2 +- rust/main/chains/hyperlane-sealevel/src/transaction.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/account.rs b/rust/main/chains/hyperlane-sealevel/src/account.rs index 8fc6d444b0..2f126f05ef 100644 --- a/rust/main/chains/hyperlane-sealevel/src/account.rs +++ b/rust/main/chains/hyperlane-sealevel/src/account.rs @@ -18,7 +18,7 @@ pub async fn search_accounts_by_discriminator( offset: usize, length: usize, ) -> ChainResult> { - let target_message_account_bytes = &[&discriminator[..], &nonce_bytes[..]].concat(); + let target_message_account_bytes = &[discriminator, nonce_bytes].concat(); let target_message_account_bytes = Base64.encode(target_message_account_bytes); // First, find all accounts with the matching account data. diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index 6436267e27..3d24fe1063 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -124,7 +124,7 @@ impl SealevelInterchainGasPaymasterIndexer { let accounts = search_accounts_by_discriminator( &self.rpc_client, &self.igp.program_id, - &discriminator, + discriminator, &sequence_number_bytes, UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET, length, diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index bf1b1e3e2e..e939a18f77 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -71,7 +71,7 @@ pub fn search_dispatched_message_transactions( OptionSerializer::None | OptionSerializer::Skip => return None, }; - let instructions = vec![message.instructions, inner_instructions].concat(); + let instructions = [message.instructions, inner_instructions].concat(); Some((index, transaction_hash, message.account_keys, instructions)) }) From f4d561295ba211d306b468f8377b3128d6d4a7e7 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:31:17 +0000 Subject: [PATCH 19/26] More clippy --- rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index 3d24fe1063..ad81d7f444 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -134,7 +134,7 @@ impl SealevelInterchainGasPaymasterIndexer { tracing::debug!(accounts=?accounts, "Fetched program accounts"); let valid_payment_pda_pubkey = search_and_validate_account(accounts, |account| { - self.interchain_payment_account(&account) + self.interchain_payment_account(account) })?; // Now that we have the valid gas payment PDA pubkey, we can get the full account data. From 097e38a97c918470302a069ec080792423912a95 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:40:16 +0000 Subject: [PATCH 20/26] Add logging for cases we don't expect to happen --- .../hyperlane-sealevel/src/transaction.rs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index e939a18f77..15ff4f827e 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; +use hyperlane_sealevel_mailbox::instruction::Instruction; use solana_sdk::pubkey::Pubkey; use solana_transaction_status::option_serializer::OptionSerializer; use solana_transaction_status::{ EncodedTransaction, EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction, UiMessage, }; +use tracing::warn; use hyperlane_core::H512; -use hyperlane_sealevel_mailbox::instruction::Instruction; use crate::utils::{decode_h512, from_base58}; @@ -40,23 +41,38 @@ pub fn search_dispatched_message_transactions( // We support only transactions encoded as JSON // We need none-empty metadata as well (EncodedTransaction::Json(t), Some(m)) => Some((index, t, m)), - _ => None, + t => { + warn!( + ?t, + "transaction is not encoded as json or metadata is empty" + ); + None + } }) .filter_map(|(index, tx, meta)| { let transaction_hash = match tx.signatures.first() { Some(h) => h, - None => return None, // if transaction is not signed, we continue the search + None => { + warn!("transaction does not have any signatures"); + return None; + } // if transaction is not signed, we continue the search }; let transaction_hash = match decode_h512(transaction_hash) { Ok(h) => h, - Err(_) => return None, // if we cannot parse transaction hash, we continue the search + Err(_) => { + warn!(?transaction_hash, "cannot decode transaction hash"); + return None; + } // if we cannot parse transaction hash, we continue the search }; // We support only Raw messages initially let message = match tx.message { UiMessage::Raw(m) => m, - _ => return None, + _ => { + warn!("we expect messages in Raw format"); + return None; + } }; let inner_instructions = match meta.inner_instructions { From 3dbdb510d2ea4bacd0e7a3cf0e942b31a10497b5 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:42:21 +0000 Subject: [PATCH 21/26] Populate log index using nonce --- rust/main/chains/hyperlane-sealevel/src/mailbox.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 340152e83b..0df14627f5 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -726,12 +726,10 @@ impl SealevelMailboxIndexer { LogMeta { address: self.mailbox.program_id.to_bytes().into(), block_number: dispatched_message_account.slot, - // TODO: get these when building out scraper support. - // It's inconvenient to get these :| block_hash, transaction_id: transaction_hash, transaction_index: transaction_index as u64, - log_index: U256::zero(), + log_index: U256::from(nonce), }, )) } From 0c396eba71fc73fec579ab4e710c81190d12064b Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:04:49 +0000 Subject: [PATCH 22/26] Add TODO comment to SealevelProvider --- rust/main/chains/hyperlane-sealevel/src/provider.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index ba34cb69c6..a0c5a41ead 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -68,6 +68,12 @@ impl HyperlaneProvider for SealevelProvider { Ok(block_info) } + /// TODO This method is superfluous for Solana. + /// Since we have to request full block to find transaction hash and transaction index + /// for Solana, we have all the data about transaction mach earlier before this + /// method is invoked. + /// We can refactor abstractions so that our chain-agnostic code is more suitable + /// for all chains, not only Ethereum-like chains. async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { let signature = Signature::new(hash.as_bytes()); let transaction = self.rpc_client.get_transaction(&signature).await?; From 51ba612200912011e8dfd127792c174991e2f4e4 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:31:32 +0000 Subject: [PATCH 23/26] Extract functions for readability --- .../hyperlane-sealevel/src/transaction.rs | 253 ++++++++++-------- 1 file changed, 148 insertions(+), 105 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 15ff4f827e..9c49962c76 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -5,7 +5,7 @@ use solana_sdk::pubkey::Pubkey; use solana_transaction_status::option_serializer::OptionSerializer; use solana_transaction_status::{ EncodedTransaction, EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction, - UiMessage, + UiMessage, UiTransaction, UiTransactionStatusMeta, }; use tracing::warn; @@ -37,116 +37,159 @@ pub fn search_dispatched_message_transactions( transactions .into_iter() .enumerate() - .filter_map(|(index, tx)| match (tx.transaction, tx.meta) { - // We support only transactions encoded as JSON - // We need none-empty metadata as well - (EncodedTransaction::Json(t), Some(m)) => Some((index, t, m)), - t => { - warn!( - ?t, - "transaction is not encoded as json or metadata is empty" - ); - None - } - }) + .filter_map(|(index, tx)| filter_by_encoding(tx).map(|(tx, meta)| (index, tx, meta))) .filter_map(|(index, tx, meta)| { - let transaction_hash = match tx.signatures.first() { - Some(h) => h, - None => { - warn!("transaction does not have any signatures"); - return None; - } // if transaction is not signed, we continue the search - }; - - let transaction_hash = match decode_h512(transaction_hash) { - Ok(h) => h, - Err(_) => { - warn!(?transaction_hash, "cannot decode transaction hash"); - return None; - } // if we cannot parse transaction hash, we continue the search - }; - - // We support only Raw messages initially - let message = match tx.message { - UiMessage::Raw(m) => m, - _ => { - warn!("we expect messages in Raw format"); - return None; - } - }; - - let inner_instructions = match meta.inner_instructions { - OptionSerializer::Some(ii) => ii - .into_iter() - .flat_map(|ii| ii.instructions) - .flat_map(|ii| match ii { - UiInstruction::Compiled(ci) => Some(ci), - _ => None, - }) - .collect::>(), - OptionSerializer::None | OptionSerializer::Skip => return None, - }; - - let instructions = [message.instructions, inner_instructions].concat(); - - Some((index, transaction_hash, message.account_keys, instructions)) + filter_by_validity(tx, meta) + .map(|(hash, account_keys, instructions)| (index, hash, account_keys, instructions)) }) .filter_map(|(index, hash, account_keys, instructions)| { - let account_keys = account_keys - .into_iter() - .enumerate() - .map(|(index, key)| (key, index)) - .collect::>(); - - let mailbox_program_id_str = mailbox_program_id.to_string(); - let mailbox_program_index = match account_keys.get(&mailbox_program_id_str) { - Some(i) => *i as u8, - None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch. - }; - - let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string(); - let dispatch_message_pda_account_index = - match account_keys.get(&message_storage_pda_pubkey_str) { - Some(i) => *i as u8, - None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch. - }; - - let mailbox_program_maybe = instructions - .into_iter() - .find(|instruction| instruction.program_id_index == mailbox_program_index); - - let mailbox_program = match mailbox_program_maybe { - Some(p) => p, - None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch. - }; - - // If Mailbox program does not operate on dispatch message store PDA account, transaction is not message dispatch. - if !mailbox_program - .accounts - .contains(&dispatch_message_pda_account_index) - { - return None; - } - - let instruction_data = match from_base58(&mailbox_program.data) { - Ok(d) => d, - Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch. - }; - - let instruction = match Instruction::from_instruction_data(&instruction_data) { - Ok(m) => m, - Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch. - }; - - // If the call into Mailbox program is not OutboxDispatch, transaction is not message dispatch. - if !matches!(instruction, Instruction::OutboxDispatch(_)) { - return None; - } - - Some((index, hash)) + filter_not_relevant( + mailbox_program_id, + message_storage_pda_pubkey, + hash, + account_keys, + instructions, + ) + .map(|hash| (index, hash)) }) .collect::>() } +fn filter_not_relevant( + mailbox_program_id: &Pubkey, + message_storage_pda_pubkey: &Pubkey, + hash: H512, + account_keys: Vec, + instructions: Vec, +) -> Option { + let account_index_map = account_index_map(account_keys); + + let mailbox_program_id_str = mailbox_program_id.to_string(); + let mailbox_program_index = match account_index_map.get(&mailbox_program_id_str) { + Some(i) => *i as u8, + None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch. + }; + + let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string(); + let dispatch_message_pda_account_index = + match account_index_map.get(&message_storage_pda_pubkey_str) { + Some(i) => *i as u8, + None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch. + }; + + let mailbox_program_maybe = instructions + .into_iter() + .find(|instruction| instruction.program_id_index == mailbox_program_index); + + let mailbox_program = match mailbox_program_maybe { + Some(p) => p, + None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch. + }; + + // If Mailbox program does not operate on dispatch message store PDA account, transaction is not message dispatch. + if !mailbox_program + .accounts + .contains(&dispatch_message_pda_account_index) + { + return None; + } + + let instruction_data = match from_base58(&mailbox_program.data) { + Ok(d) => d, + Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch. + }; + + let instruction = match Instruction::from_instruction_data(&instruction_data) { + Ok(ii) => ii, + Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch. + }; + + // If the call into Mailbox program is not OutboxDispatch, transaction is not message dispatch. + if !matches!(instruction, Instruction::OutboxDispatch(_)) { + return None; + } + + Some(hash) +} + +fn filter_by_validity( + tx: UiTransaction, + meta: UiTransactionStatusMeta, +) -> Option<(H512, Vec, Vec)> { + let transaction_hash = match tx.signatures.first() { + Some(h) => h, + None => { + warn!(transaction = ?tx, "transaction does not have any signatures"); + return None; + } // if transaction is not signed, we continue the search + }; + + let transaction_hash = match decode_h512(transaction_hash) { + Ok(h) => h, + Err(e) => { + warn!(?transaction_hash, ?e, "cannot decode transaction hash"); + return None; + } // if we cannot parse transaction hash, we continue the search + }; + + // We support only Raw messages initially + let message = match tx.message { + UiMessage::Raw(m) => m, + m => { + warn!(message = ?m, "we expect messages in Raw format"); + return None; + } + }; + + let instructions = instructions(message.instructions, meta); + + Some((transaction_hash, message.account_keys, instructions)) +} + +fn filter_by_encoding( + tx: EncodedTransactionWithStatusMeta, +) -> Option<(UiTransaction, UiTransactionStatusMeta)> { + match (tx.transaction, tx.meta) { + // We support only transactions encoded as JSON + // We need none-empty metadata as well + (EncodedTransaction::Json(t), Some(m)) => Some((t, m)), + t => { + warn!( + ?t, + "transaction is not encoded as json or metadata is empty" + ); + None + } + } +} + +fn account_index_map(account_keys: Vec) -> HashMap { + account_keys + .into_iter() + .enumerate() + .map(|(index, key)| (key, index)) + .collect::>() +} + +/// Extract all instructions from transaction +fn instructions( + instruction: Vec, + meta: UiTransactionStatusMeta, +) -> Vec { + let inner_instructions = match meta.inner_instructions { + OptionSerializer::Some(ii) => ii + .into_iter() + .flat_map(|ii| ii.instructions) + .flat_map(|ii| match ii { + UiInstruction::Compiled(ci) => Some(ci), + _ => None, + }) + .collect::>(), + OptionSerializer::None | OptionSerializer::Skip => vec![], + }; + + [instruction, inner_instructions].concat() +} + #[cfg(test)] mod tests; From 1187874168bb183673a65b569d4c4a1619d56e6b Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:00:49 +0000 Subject: [PATCH 24/26] Make variable names more precise --- .../hyperlane-sealevel/src/interchain_gas.rs | 4 +- .../chains/hyperlane-sealevel/src/mailbox.rs | 41 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index ad81d7f444..3c2cbd1c16 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -120,14 +120,14 @@ impl SealevelInterchainGasPaymasterIndexer { ) -> ChainResult { let discriminator = hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR; let sequence_number_bytes = sequence_number.to_le_bytes(); - let length = 32; // the length of the `unique_gas_payment_pubkey` field + let unique_gas_payment_pubkey_length = 32; // the length of the `unique_gas_payment_pubkey` field let accounts = search_accounts_by_discriminator( &self.rpc_client, &self.igp.program_id, discriminator, &sequence_number_bytes, UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET, - length, + unique_gas_payment_pubkey_length, ) .await?; diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 0df14627f5..45e4461279 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -4,21 +4,13 @@ use std::{collections::HashMap, num::NonZeroU64, ops::RangeInclusive, str::FromS use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; -use hyperlane_core::{ - 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, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome, - H256, H512, U256, -}; use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, }; use hyperlane_sealevel_mailbox::{ accounts::{ DispatchedMessageAccount, InboxAccount, OutboxAccount, ProcessedMessage, - ProcessedMessageAccount, + ProcessedMessageAccount, DISPATCHED_MESSAGE_DISCRIMINATOR, PROCESSED_MESSAGE_DISCRIMINATOR, }, instruction, instruction::InboxProcess, @@ -58,6 +50,15 @@ use solana_transaction_status::{ }; use tracing::{debug, info, instrument, warn}; +use hyperlane_core::{ + 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, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome, + H256, H512, U256, +}; + use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; use crate::error::HyperlaneSealevelError; use crate::transaction::search_dispatched_message_transactions; @@ -664,17 +665,16 @@ impl SealevelMailboxIndexer { &self, nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { - let discriminator = hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR; let nonce_bytes = nonce.to_le_bytes(); - let offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field - let length = 32; // the length of the `unique_message_pubkey` field + let unique_dispatched_message_pubkey_offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field + let unique_dispatch_message_pubkey_length = 32; // the length of the `unique_message_pubkey` field let accounts = search_accounts_by_discriminator( self.rpc(), &self.program_id, - &discriminator, + &DISPATCHED_MESSAGE_DISCRIMINATOR, &nonce_bytes, - offset, - length, + unique_dispatched_message_pubkey_offset, + unique_dispatch_message_pubkey_length, ) .await?; @@ -752,17 +752,16 @@ impl SealevelMailboxIndexer { &self, nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { - let discriminator = hyperlane_sealevel_mailbox::accounts::PROCESSED_MESSAGE_DISCRIMINATOR; let nonce_bytes = nonce.to_le_bytes(); - let offset = 1 + 8 + 8; // the offset to get the `message_id` field - let length = 32; + let delivered_message_id_offset = 1 + 8 + 8; // the offset to get the `message_id` field + let delivered_message_id_length = 32; let accounts = search_accounts_by_discriminator( self.rpc(), &self.program_id, - &discriminator, + &PROCESSED_MESSAGE_DISCRIMINATOR, &nonce_bytes, - offset, - length, + delivered_message_id_offset, + delivered_message_id_length, ) .await?; From 2843126fd6f359dd4a6466783a428c83ac0a5616 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:18:48 +0000 Subject: [PATCH 25/26] Refactor --- .../hyperlane-sealevel/src/transaction.rs | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs index 9c49962c76..26a0722cf2 100644 --- a/rust/main/chains/hyperlane-sealevel/src/transaction.rs +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -116,29 +116,22 @@ fn filter_by_validity( tx: UiTransaction, meta: UiTransactionStatusMeta, ) -> Option<(H512, Vec, Vec)> { - let transaction_hash = match tx.signatures.first() { - Some(h) => h, - None => { - warn!(transaction = ?tx, "transaction does not have any signatures"); - return None; - } // if transaction is not signed, we continue the search - }; - - let transaction_hash = match decode_h512(transaction_hash) { - Ok(h) => h, - Err(e) => { - warn!(?transaction_hash, ?e, "cannot decode transaction hash"); - return None; - } // if we cannot parse transaction hash, we continue the search + let Some(transaction_hash) = tx + .signatures + .first() + .map(|signature| decode_h512(signature)) + .and_then(|r| r.ok()) + else { + warn!( + transaction = ?tx, + "transaction does not have any signatures or signatures cannot be decoded", + ); + return None; }; - // We support only Raw messages initially - let message = match tx.message { - UiMessage::Raw(m) => m, - m => { - warn!(message = ?m, "we expect messages in Raw format"); - return None; - } + let UiMessage::Raw(message) = tx.message else { + warn!(message = ?tx.message, "we expect messages in Raw format"); + return None; }; let instructions = instructions(message.instructions, meta); From 565fa21a83a39b1b8ed391849d649160c0c9355a Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:33:42 +0000 Subject: [PATCH 26/26] Refactor --- rust/main/chains/hyperlane-sealevel/src/account.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rust/main/chains/hyperlane-sealevel/src/account.rs b/rust/main/chains/hyperlane-sealevel/src/account.rs index 2f126f05ef..f671c432e8 100644 --- a/rust/main/chains/hyperlane-sealevel/src/account.rs +++ b/rust/main/chains/hyperlane-sealevel/src/account.rs @@ -55,18 +55,14 @@ pub fn search_and_validate_account( where F: Fn(&Account) -> ChainResult, { - let mut valid_storage_pda_pubkey = Option::::None; - for (pubkey, account) in accounts { let expected_pubkey = message_account(&account)?; if expected_pubkey == pubkey { - valid_storage_pda_pubkey = Some(pubkey); - break; + return Ok(pubkey); } } - let valid_storage_pda_pubkey = valid_storage_pda_pubkey.ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find valid storage PDA pubkey") - })?; - Ok(valid_storage_pda_pubkey) + Err(ChainCommunicationError::from_other_str( + "Could not find valid storage PDA pubkey", + )) }