From e273d61f62a80b06a8ccf19bd71ce429d6a16d50 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:06:43 +0200 Subject: [PATCH 1/4] fix(rpc): mark block simulations in tx envs --- crates/alloy/src/rpc/reth_compat.rs | 5 +-- crates/alloy/src/rpc/revm_compat.rs | 12 ++---- crates/revm/src/lib.rs | 4 +- crates/revm/src/tx.rs | 59 +++++++++++++++++++++++++++-- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/crates/alloy/src/rpc/reth_compat.rs b/crates/alloy/src/rpc/reth_compat.rs index 6684662937..c3a63e7bf4 100644 --- a/crates/alloy/src/rpc/reth_compat.rs +++ b/crates/alloy/src/rpc/reth_compat.rs @@ -129,9 +129,7 @@ impl FromConsensusHeader for TempoHeaderResponse { #[cfg(test)] mod tests { use super::*; - use crate::rpc::revm_compat::{ - RPC_SIMULATION_UNIQUE_TX_IDENTIFIER, create_mock_primitive_signature, - }; + use crate::rpc::revm_compat::create_mock_primitive_signature; use alloy_primitives::{Address, B256, Bytes, TxKind, address}; use alloy_rpc_types_eth::TransactionRequest; use alloy_signer::SignerSync; @@ -141,6 +139,7 @@ mod tests { SignatureType, TempoTransaction, transaction::{Call, FEE_PAYER_SIGNATURE_MARKER, tt_signature::PrimitiveSignature}, }; + use tempo_revm::RPC_SIMULATION_UNIQUE_TX_IDENTIFIER; fn call_request(target: Address) -> TransactionRequest { TransactionRequest { diff --git a/crates/alloy/src/rpc/revm_compat.rs b/crates/alloy/src/rpc/revm_compat.rs index dd77f0014e..bfae04f566 100644 --- a/crates/alloy/src/rpc/revm_compat.rs +++ b/crates/alloy/src/rpc/revm_compat.rs @@ -6,15 +6,9 @@ use tempo_primitives::{ SignatureType, TempoSignature, transaction::{Call, RecoveredTempoAuthorization}, }; -use tempo_revm::{ExecutionContext, TempoBatchCallEnv, TempoTxEnv}; - -/// Non-zero transaction identifier used only for RPC simulations. -/// -/// RPC requests are not final signed transactions, so gas filling and other request normalization -/// can make a simulated signing payload differ from the eventual submitted transaction. Use a -/// fixed sentinel instead of deriving a misleading future channel id from the simulated payload. -pub(super) const RPC_SIMULATION_UNIQUE_TX_IDENTIFIER: B256 = - B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT"); +use tempo_revm::{ + ExecutionContext, RPC_SIMULATION_UNIQUE_TX_IDENTIFIER, TempoBatchCallEnv, TempoTxEnv, +}; impl TempoTransactionRequest { /// Applies this request's Tempo-specific fields to a normalized simulation transaction env. diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 49de5f6219..402ddef171 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -25,4 +25,6 @@ pub use fee_manager::{FeeTokenResolver, ProtocolFeeContext, ProtocolFeeManager, pub use handler::{ValidationContext, calculate_aa_batch_intrinsic_gas}; pub use revm::interpreter::instructions::utility::IntoAddress; pub use tempo_primitives::TempoBlockEnv; -pub use tx::{ExecutionContext, TempoBatchCallEnv, TempoTxEnv}; +pub use tx::{ + ExecutionContext, RPC_SIMULATION_UNIQUE_TX_IDENTIFIER, TempoBatchCallEnv, TempoTxEnv, +}; diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index 7129e2a4d0..fed39127d6 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -18,6 +18,15 @@ use tempo_primitives::{ }, }; +/// Non-zero transaction identifier used only for RPC simulations. +/// +/// RPC requests are not final signed transactions, so gas filling and other request normalization +/// can make a simulated signing payload differ from the eventual submitted transaction. Use a +/// fixed sentinel instead of deriving a misleading transaction identifier from the simulated +/// payload. +pub const RPC_SIMULATION_UNIQUE_TX_IDENTIFIER: B256 = + B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT"); + /// Tempo transaction environment for AA features. #[derive(Debug, Clone, Default)] pub struct TempoBatchCallEnv { @@ -450,15 +459,21 @@ impl FromTxWithEncoded for TempoTxEnv { } impl FromTxWithEncoded for TempoTxEnv { - fn from_encoded_tx(tx: &TempoTxEnvelope, sender: Address, _encoded: Bytes) -> Self { - Self::from_recovered_tx(tx, sender) + fn from_encoded_tx(tx: &TempoTxEnvelope, sender: Address, encoded: Bytes) -> Self { + let mut tx_env = Self::from_recovered_tx(tx, sender); + // Reth wraps eth_simulateV1 transactions with an empty encoding before execution. + if encoded.is_empty() { + tx_env.execution_context = ExecutionContext::Simulation; + tx_env.unique_tx_identifier = Some(RPC_SIMULATION_UNIQUE_TX_IDENTIFIER); + } + tx_env } } #[cfg(test)] mod tests { use alloy_consensus::{Signed, TxLegacy, transaction::TxHashRef}; - use alloy_evm::FromRecoveredTx; + use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_primitives::{Address, Bytes, Signature, TxKind, U256, keccak256}; use core::num::NonZeroU64; use proptest::prelude::*; @@ -649,6 +664,44 @@ mod tests { ); } + #[test] + fn test_empty_encoded_tx_marks_rpc_block_simulation() { + let account = Address::repeat_byte(0xAA); + let tx = tempo_primitives::transaction::TempoTransaction { + chain_id: 1, + gas_limit: 100_000, + calls: vec![create_call(TxKind::Call(Address::repeat_byte(0x42)))], + ..Default::default() + }; + let signature = + TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())); + let envelope = TempoTxEnvelope::AA(AASigned::new_unhashed(tx, signature)); + + let tx_env = TempoTxEnv::from_encoded_tx(&envelope, account, Bytes::new()); + + assert_eq!(tx_env.execution_context(), ExecutionContext::Simulation); + assert_eq!( + tx_env.channel_open_context_hash(), + Some(super::RPC_SIMULATION_UNIQUE_TX_IDENTIFIER) + ); + + let tx_env = TempoTxEnv::from_encoded_tx( + &envelope, + account, + Bytes::from_static(b"signed transaction"), + ); + assert_eq!( + tx_env.execution_context(), + ExecutionContext::Transaction { + tx_hash: *envelope.tx_hash() + } + ); + assert_ne!( + tx_env.channel_open_context_hash(), + Some(super::RPC_SIMULATION_UNIQUE_TX_IDENTIFIER) + ); + } + #[test] fn test_tx_env() { let tx_env = super::TempoTxEnv::default(); From 2878535a229c9362ce4fcdcbc767bef92d229379 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:45:21 +0200 Subject: [PATCH 2/4] fix(rpc): preserve block simulation transaction identity --- crates/alloy/src/rpc/reth_compat.rs | 5 +-- crates/alloy/src/rpc/revm_compat.rs | 12 +++++-- crates/revm/src/lib.rs | 4 +-- crates/revm/src/tx.rs | 50 +++++++++++++++++------------ 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/crates/alloy/src/rpc/reth_compat.rs b/crates/alloy/src/rpc/reth_compat.rs index c3a63e7bf4..6684662937 100644 --- a/crates/alloy/src/rpc/reth_compat.rs +++ b/crates/alloy/src/rpc/reth_compat.rs @@ -129,7 +129,9 @@ impl FromConsensusHeader for TempoHeaderResponse { #[cfg(test)] mod tests { use super::*; - use crate::rpc::revm_compat::create_mock_primitive_signature; + use crate::rpc::revm_compat::{ + RPC_SIMULATION_UNIQUE_TX_IDENTIFIER, create_mock_primitive_signature, + }; use alloy_primitives::{Address, B256, Bytes, TxKind, address}; use alloy_rpc_types_eth::TransactionRequest; use alloy_signer::SignerSync; @@ -139,7 +141,6 @@ mod tests { SignatureType, TempoTransaction, transaction::{Call, FEE_PAYER_SIGNATURE_MARKER, tt_signature::PrimitiveSignature}, }; - use tempo_revm::RPC_SIMULATION_UNIQUE_TX_IDENTIFIER; fn call_request(target: Address) -> TransactionRequest { TransactionRequest { diff --git a/crates/alloy/src/rpc/revm_compat.rs b/crates/alloy/src/rpc/revm_compat.rs index bfae04f566..dd77f0014e 100644 --- a/crates/alloy/src/rpc/revm_compat.rs +++ b/crates/alloy/src/rpc/revm_compat.rs @@ -6,9 +6,15 @@ use tempo_primitives::{ SignatureType, TempoSignature, transaction::{Call, RecoveredTempoAuthorization}, }; -use tempo_revm::{ - ExecutionContext, RPC_SIMULATION_UNIQUE_TX_IDENTIFIER, TempoBatchCallEnv, TempoTxEnv, -}; +use tempo_revm::{ExecutionContext, TempoBatchCallEnv, TempoTxEnv}; + +/// Non-zero transaction identifier used only for RPC simulations. +/// +/// RPC requests are not final signed transactions, so gas filling and other request normalization +/// can make a simulated signing payload differ from the eventual submitted transaction. Use a +/// fixed sentinel instead of deriving a misleading future channel id from the simulated payload. +pub(super) const RPC_SIMULATION_UNIQUE_TX_IDENTIFIER: B256 = + B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT"); impl TempoTransactionRequest { /// Applies this request's Tempo-specific fields to a normalized simulation transaction env. diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 402ddef171..49de5f6219 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -25,6 +25,4 @@ pub use fee_manager::{FeeTokenResolver, ProtocolFeeContext, ProtocolFeeManager, pub use handler::{ValidationContext, calculate_aa_batch_intrinsic_gas}; pub use revm::interpreter::instructions::utility::IntoAddress; pub use tempo_primitives::TempoBlockEnv; -pub use tx::{ - ExecutionContext, RPC_SIMULATION_UNIQUE_TX_IDENTIFIER, TempoBatchCallEnv, TempoTxEnv, -}; +pub use tx::{ExecutionContext, TempoBatchCallEnv, TempoTxEnv}; diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index fed39127d6..c910e53d3a 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -18,15 +18,6 @@ use tempo_primitives::{ }, }; -/// Non-zero transaction identifier used only for RPC simulations. -/// -/// RPC requests are not final signed transactions, so gas filling and other request normalization -/// can make a simulated signing payload differ from the eventual submitted transaction. Use a -/// fixed sentinel instead of deriving a misleading transaction identifier from the simulated -/// payload. -pub const RPC_SIMULATION_UNIQUE_TX_IDENTIFIER: B256 = - B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT"); - /// Tempo transaction environment for AA features. #[derive(Debug, Clone, Default)] pub struct TempoBatchCallEnv { @@ -464,7 +455,6 @@ impl FromTxWithEncoded for TempoTxEnv { // Reth wraps eth_simulateV1 transactions with an empty encoding before execution. if encoded.is_empty() { tx_env.execution_context = ExecutionContext::Simulation; - tx_env.unique_tx_identifier = Some(RPC_SIMULATION_UNIQUE_TX_IDENTIFIER); } tx_env } @@ -667,22 +657,40 @@ mod tests { #[test] fn test_empty_encoded_tx_marks_rpc_block_simulation() { let account = Address::repeat_byte(0xAA); - let tx = tempo_primitives::transaction::TempoTransaction { - chain_id: 1, - gas_limit: 100_000, - calls: vec![create_call(TxKind::Call(Address::repeat_byte(0x42)))], - ..Default::default() + let make_envelope = |nonce| { + let tx = tempo_primitives::transaction::TempoTransaction { + chain_id: 1, + gas_limit: 100_000, + calls: vec![create_call(TxKind::Call(Address::repeat_byte(0x42)))], + nonce, + ..Default::default() + }; + let signature = TempoSignature::Primitive(PrimitiveSignature::Secp256k1( + Signature::test_signature(), + )); + TempoTxEnvelope::AA(AASigned::new_unhashed(tx, signature)) }; - let signature = - TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature())); - let envelope = TempoTxEnvelope::AA(AASigned::new_unhashed(tx, signature)); + let envelope = make_envelope(0); + let expected_identifier = envelope.unique_tx_identifier(account); let tx_env = TempoTxEnv::from_encoded_tx(&envelope, account, Bytes::new()); assert_eq!(tx_env.execution_context(), ExecutionContext::Simulation); assert_eq!( tx_env.channel_open_context_hash(), - Some(super::RPC_SIMULATION_UNIQUE_TX_IDENTIFIER) + Some(expected_identifier) + ); + + let next_envelope = make_envelope(1); + let next_tx_env = TempoTxEnv::from_encoded_tx(&next_envelope, account, Bytes::new()); + assert_eq!( + next_tx_env.execution_context(), + ExecutionContext::Simulation + ); + assert_ne!( + next_tx_env.channel_open_context_hash(), + tx_env.channel_open_context_hash(), + "distinct simulated transactions must retain distinct replay identities" ); let tx_env = TempoTxEnv::from_encoded_tx( @@ -696,9 +704,9 @@ mod tests { tx_hash: *envelope.tx_hash() } ); - assert_ne!( + assert_eq!( tx_env.channel_open_context_hash(), - Some(super::RPC_SIMULATION_UNIQUE_TX_IDENTIFIER) + Some(expected_identifier) ); } From bcc2c20ff04afee4c6a9cf67ec13fa77e1fc9587 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:49:59 +0200 Subject: [PATCH 3/4] fix(rpc): align encoded simulation conversions --- crates/revm/src/tx.rs | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index c910e53d3a..e2f98ffad8 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -444,19 +444,30 @@ impl FromRecoveredTx for TempoTxEnv { } impl FromTxWithEncoded for TempoTxEnv { - fn from_encoded_tx(tx: &AASigned, sender: Address, _encoded: Bytes) -> Self { - Self::from_recovered_tx(tx, sender) + fn from_encoded_tx(tx: &AASigned, sender: Address, encoded: Bytes) -> Self { + let mut tx_env = Self::from_recovered_tx(tx, sender); + tx_env.mark_rpc_block_simulation(&encoded); + tx_env } } impl FromTxWithEncoded for TempoTxEnv { fn from_encoded_tx(tx: &TempoTxEnvelope, sender: Address, encoded: Bytes) -> Self { let mut tx_env = Self::from_recovered_tx(tx, sender); - // Reth wraps eth_simulateV1 transactions with an empty encoding before execution. + tx_env.mark_rpc_block_simulation(&encoded); + tx_env + } +} + +impl TempoTxEnv { + /// Marks the empty envelope Reth intentionally uses for `eth_simulateV1` transactions. + /// + /// Real signed transactions always have a non-empty EIP-2718 encoding. Reth uses an empty + /// encoding only for block simulations so execution layers can omit envelope-derived costs. + fn mark_rpc_block_simulation(&mut self, encoded: &Bytes) { if encoded.is_empty() { - tx_env.execution_context = ExecutionContext::Simulation; + self.execution_context = ExecutionContext::Simulation; } - tx_env } } @@ -657,7 +668,7 @@ mod tests { #[test] fn test_empty_encoded_tx_marks_rpc_block_simulation() { let account = Address::repeat_byte(0xAA); - let make_envelope = |nonce| { + let make_signed = |nonce| { let tx = tempo_primitives::transaction::TempoTransaction { chain_id: 1, gas_limit: 100_000, @@ -668,9 +679,10 @@ mod tests { let signature = TempoSignature::Primitive(PrimitiveSignature::Secp256k1( Signature::test_signature(), )); - TempoTxEnvelope::AA(AASigned::new_unhashed(tx, signature)) + AASigned::new_unhashed(tx, signature) }; - let envelope = make_envelope(0); + let signed = make_signed(0); + let envelope = TempoTxEnvelope::AA(signed.clone()); let expected_identifier = envelope.unique_tx_identifier(account); let tx_env = TempoTxEnv::from_encoded_tx(&envelope, account, Bytes::new()); @@ -681,7 +693,7 @@ mod tests { Some(expected_identifier) ); - let next_envelope = make_envelope(1); + let next_envelope = TempoTxEnvelope::AA(make_signed(1)); let next_tx_env = TempoTxEnv::from_encoded_tx(&next_envelope, account, Bytes::new()); assert_eq!( next_tx_env.execution_context(), @@ -708,6 +720,17 @@ mod tests { tx_env.channel_open_context_hash(), Some(expected_identifier) ); + + let direct_aa_env = TempoTxEnv::from_encoded_tx(&signed, account, Bytes::new()); + assert_eq!( + direct_aa_env.execution_context(), + ExecutionContext::Simulation, + "bare AA and envelope conversions must agree on the empty simulation encoding" + ); + assert_eq!( + direct_aa_env.channel_open_context_hash(), + Some(expected_identifier) + ); } #[test] From 3f9d040ef87ad14b10a46f1120bedd327a37f0b5 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:59:16 +0200 Subject: [PATCH 4/4] refactor(rpc): clarify simulation marker helper --- crates/revm/src/tx.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index e2f98ffad8..26dfaf8ac5 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -446,7 +446,7 @@ impl FromRecoveredTx for TempoTxEnv { impl FromTxWithEncoded for TempoTxEnv { fn from_encoded_tx(tx: &AASigned, sender: Address, encoded: Bytes) -> Self { let mut tx_env = Self::from_recovered_tx(tx, sender); - tx_env.mark_rpc_block_simulation(&encoded); + tx_env.maybe_mark_rpc_block_simulation(&encoded); tx_env } } @@ -454,7 +454,7 @@ impl FromTxWithEncoded for TempoTxEnv { impl FromTxWithEncoded for TempoTxEnv { fn from_encoded_tx(tx: &TempoTxEnvelope, sender: Address, encoded: Bytes) -> Self { let mut tx_env = Self::from_recovered_tx(tx, sender); - tx_env.mark_rpc_block_simulation(&encoded); + tx_env.maybe_mark_rpc_block_simulation(&encoded); tx_env } } @@ -464,7 +464,7 @@ impl TempoTxEnv { /// /// Real signed transactions always have a non-empty EIP-2718 encoding. Reth uses an empty /// encoding only for block simulations so execution layers can omit envelope-derived costs. - fn mark_rpc_block_simulation(&mut self, encoded: &Bytes) { + fn maybe_mark_rpc_block_simulation(&mut self, encoded: &Bytes) { if encoded.is_empty() { self.execution_context = ExecutionContext::Simulation; }