feat: Scraper report mailbox as recipient of transactions for Sealevel (#4813)

### Description

Scraper reports mailbox as recipient of transactions for Sealevel

### Related issues

- Contributes into
https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4272

### Backward compatibility

Yes

### Testing

Manual test of Scraper
E2E tests for Ethereum and Sealevel

---------

Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com>
pull/4817/head
Danil Nemirovsky 2 weeks ago committed by GitHub
parent fa424826c0
commit 8e3085ad0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      rust/main/Cargo.lock
  2. 1
      rust/main/Cargo.toml
  3. 1
      rust/main/chains/hyperlane-sealevel/Cargo.toml
  4. 6
      rust/main/chains/hyperlane-sealevel/src/error.rs
  5. 72
      rust/main/chains/hyperlane-sealevel/src/provider.rs

@ -4629,6 +4629,7 @@ dependencies = [
"hyperlane-sealevel-multisig-ism-message-id",
"hyperlane-sealevel-validator-announce",
"jsonrpc-core",
"lazy_static",
"multisig-ism",
"num-traits",
"reqwest",

@ -83,6 +83,7 @@ itertools = "*"
jobserver = "=0.1.26"
jsonrpc-core = "18.0"
k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] }
lazy_static = "1.5.0"
log = "0.4"
macro_rules_attribute = "0.2"
maplit = "1.0"

@ -11,6 +11,7 @@ bincode.workspace = true
borsh.workspace = true
derive-new.workspace = true
jsonrpc-core.workspace = true
lazy_static.workspace = true
num-traits.workspace = true
reqwest.workspace = true
serde.workspace = true

@ -42,6 +42,12 @@ pub enum HyperlaneSealevelError {
/// Empty compute units consumed
#[error("received empty compute units consumed in transaction")]
EmptyComputeUnitsConsumed,
/// Too many non-native programs
#[error("transaction contains too many non-native programs, hash: {0:?}")]
TooManyNonNativePrograms(H512),
/// No non-native programs
#[error("transaction contains no non-native programs, hash: {0:?}")]
NoNonNativePrograms(H512),
}
impl From<HyperlaneSealevelError> for ChainCommunicationError {

@ -1,10 +1,13 @@
use std::collections::HashSet;
use std::sync::Arc;
use async_trait::async_trait;
use lazy_static::lazy_static;
use solana_sdk::signature::Signature;
use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransaction, EncodedTransactionWithStatusMeta,
UiMessage, UiTransaction, UiTransactionStatusMeta,
UiInstruction, UiMessage, UiParsedInstruction, UiParsedMessage, UiTransaction,
UiTransactionStatusMeta,
};
use tracing::warn;
@ -18,6 +21,19 @@ use crate::error::HyperlaneSealevelError;
use crate::utils::{decode_h256, decode_h512, decode_pubkey};
use crate::{ConnectionConf, SealevelRpcClient};
lazy_static! {
static ref NATIVE_PROGRAMS: HashSet<String> = HashSet::from([
solana_sdk::bpf_loader_upgradeable::ID.to_string(),
solana_sdk::compute_budget::ID.to_string(),
solana_sdk::config::program::ID.to_string(),
solana_sdk::ed25519_program::ID.to_string(),
solana_sdk::secp256k1_program::ID.to_string(),
solana_sdk::stake::program::ID.to_string(),
solana_sdk::system_program::ID.to_string(),
solana_sdk::vote::program::ID.to_string(),
]);
}
/// A wrapper around a Sealevel provider to get generic blockchain information.
#[derive(Debug)]
pub struct SealevelProvider {
@ -33,7 +49,7 @@ impl SealevelProvider {
let rpc_client = Arc::new(SealevelRpcClient::new(conf.url.to_string()));
let native_token = conf.native_token.clone();
SealevelProvider {
Self {
domain,
rpc_client,
native_token,
@ -64,12 +80,7 @@ impl SealevelProvider {
}
fn sender(hash: &H512, txn: &UiTransaction) -> ChainResult<H256> {
let message = match &txn.message {
UiMessage::Parsed(m) => m,
m => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedMessageEncoding(m.clone()),
))?,
};
let message = Self::parsed_message(txn)?;
let signer = message
.account_keys
@ -80,6 +91,39 @@ impl SealevelProvider {
Ok(sender)
}
fn recipient(hash: &H512, txn: &UiTransaction) -> ChainResult<H256> {
let message = Self::parsed_message(txn)?;
let programs = message
.instructions
.iter()
.filter_map(|ii| {
if let UiInstruction::Parsed(iii) = ii {
Some(iii)
} else {
None
}
})
.map(|ii| match ii {
UiParsedInstruction::Parsed(iii) => &iii.program_id,
UiParsedInstruction::PartiallyDecoded(iii) => &iii.program_id,
})
.filter(|program_id| !NATIVE_PROGRAMS.contains(*program_id))
.collect::<Vec<&String>>();
if programs.len() > 1 {
Err(HyperlaneSealevelError::TooManyNonNativePrograms(*hash))?;
}
let program_id = programs
.first()
.ok_or(HyperlaneSealevelError::NoNonNativePrograms(*hash))?;
let pubkey = decode_pubkey(program_id)?;
let recipient = H256::from_slice(&pubkey.to_bytes());
Ok(recipient)
}
fn gas(meta: &UiTransactionStatusMeta) -> ChainResult<U256> {
let OptionSerializer::Some(gas) = meta.compute_units_consumed else {
Err(HyperlaneSealevelError::EmptyComputeUnitsConsumed)?
@ -108,6 +152,15 @@ impl SealevelProvider {
.ok_or(HyperlaneSealevelError::EmptyMetadata)?;
Ok(meta)
}
fn parsed_message(txn: &UiTransaction) -> ChainResult<&UiParsedMessage> {
Ok(match &txn.message {
UiMessage::Parsed(m) => m,
m => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedMessageEncoding(m.clone()),
))?,
})
}
}
impl HyperlaneChain for SealevelProvider {
@ -165,6 +218,7 @@ impl HyperlaneProvider for SealevelProvider {
Self::validate_transaction(hash, txn)?;
let sender = Self::sender(hash, txn)?;
let recipient = Self::recipient(hash, txn)?;
let meta = Self::meta(txn_with_meta)?;
let gas_used = Self::gas(meta)?;
let fee = self.fee(meta)?;
@ -189,7 +243,7 @@ impl HyperlaneProvider for SealevelProvider {
gas_price,
nonce: 0,
sender,
recipient: None,
recipient: Some(recipient),
receipt: Some(receipt),
raw_input_data: None,
})

Loading…
Cancel
Save