diff --git a/crates/solana-gateway-task-processor/src/component.rs b/crates/solana-gateway-task-processor/src/component.rs index 361b1bd..4151cfb 100644 --- a/crates/solana-gateway-task-processor/src/component.rs +++ b/crates/solana-gateway-task-processor/src/component.rs @@ -16,7 +16,8 @@ use axelar_solana_encoding::borsh::BorshDeserialize as _; use axelar_solana_encoding::types::execute_data::{ExecuteData, MerkleisedPayload}; use axelar_solana_encoding::types::messages::{CrossChainId, Message}; use axelar_solana_gateway::error::GatewayError; -use axelar_solana_gateway::state::incoming_message::command_id; +use axelar_solana_gateway::state::incoming_message::{command_id, IncomingMessage, MessageStatus}; +use axelar_solana_gateway::BytemuckedPda as _; use effective_tx_sender::ComputeBudgetError; use eyre::{Context as _, OptionExt as _}; use futures::stream::{FusedStream as _, FuturesOrdered, FuturesUnordered}; @@ -342,6 +343,11 @@ async fn execute_task( let (gateway_incoming_message_pda, ..) = axelar_solana_gateway::get_incoming_message_pda(&command_id); + if incoming_message_already_executed(solana_rpc_client, &gateway_incoming_message_pda).await? { + tracing::warn!("incoming message already executed"); + return Ok(()); + } + // Upload the message payload to a Gateway-owned PDA account and get its address back. let gateway_message_payload_pda = message_payload::upload( solana_rpc_client, @@ -398,6 +404,20 @@ async fn execute_task( Ok(()) } +/// Checks if the incoming message has already been executed. +async fn incoming_message_already_executed( + solana_rpc_client: &RpcClient, + incoming_message_pda: &Pubkey, +) -> eyre::Result { + let raw_incoming_message = solana_rpc_client + .get_account_data(incoming_message_pda) + .await?; + let incoming_message = IncomingMessage::read(&raw_incoming_message) + .ok_or_eyre("failed to read incoming message")?; + + Ok(incoming_message.status == MessageStatus::Executed) +} + /// Validates that the relayer's signing account is not included in the transaction payload. /// /// This is a critical security check to prevent potential account draining attacks. Since the