Skip to main content

tempo_evm/
engine.rs

1use crate::TempoEvmConfig;
2use alloy_consensus::crypto::RecoveryError;
3use alloy_primitives::Address;
4use reth_evm::{
5    ConfigureEngineEvm, ConfigureEvm, EvmEnvFor, ExecutableTxIterator, ExecutionCtxFor,
6    FromRecoveredTx, RecoveredTx, ToTxEnv, block::ExecutableTxParts,
7};
8use reth_primitives_traits::{SealedBlock, SignedTransaction};
9use std::sync::Arc;
10use tempo_payload_types::TempoExecutionData;
11use tempo_primitives::{Block, TempoTxEnvelope};
12use tempo_revm::TempoTxEnv;
13
14impl ConfigureEngineEvm<TempoExecutionData> for TempoEvmConfig {
15    fn evm_env_for_payload(
16        &self,
17        payload: &TempoExecutionData,
18    ) -> Result<EvmEnvFor<Self>, Self::Error> {
19        self.evm_env(&payload.block)
20    }
21
22    fn context_for_payload<'a>(
23        &self,
24        payload: &'a TempoExecutionData,
25    ) -> Result<ExecutionCtxFor<'a, Self>, Self::Error> {
26        let TempoExecutionData {
27            block,
28            validator_set,
29        } = payload;
30        let mut context = self.context_for_block(block)?;
31
32        context.validator_set = validator_set.clone();
33
34        Ok(context)
35    }
36
37    fn tx_iterator_for_payload(
38        &self,
39        payload: &TempoExecutionData,
40    ) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
41        let block = payload.block.clone();
42        let transactions: Vec<_> = (0..payload.block.body().transactions.len())
43            .map(move |i| (block.clone(), i))
44            .collect();
45
46        Ok((transactions, RecoveredInBlock::new))
47    }
48}
49
50/// A [`reth_evm::execute::ExecutableTxFor`] implementation that contains a pointer to the
51/// block and the transaction index, allowing to prepare a [`TempoTxEnv`] without having to
52/// clone block or transaction.
53#[derive(Clone)]
54struct RecoveredInBlock {
55    block: Arc<SealedBlock<Block>>,
56    index: usize,
57    sender: Address,
58}
59
60impl RecoveredInBlock {
61    fn new((block, index): (Arc<SealedBlock<Block>>, usize)) -> Result<Self, RecoveryError> {
62        let sender = block.body().transactions[index].try_recover()?;
63        Ok(Self {
64            block,
65            index,
66            sender,
67        })
68    }
69}
70
71impl RecoveredTx<TempoTxEnvelope> for RecoveredInBlock {
72    fn tx(&self) -> &TempoTxEnvelope {
73        &self.block.body().transactions[self.index]
74    }
75
76    fn signer(&self) -> &alloy_primitives::Address {
77        &self.sender
78    }
79}
80
81impl ToTxEnv<TempoTxEnv> for RecoveredInBlock {
82    fn to_tx_env(&self) -> TempoTxEnv {
83        TempoTxEnv::from_recovered_tx(self.tx(), *self.signer())
84    }
85}
86
87impl ExecutableTxParts<TempoTxEnv, TempoTxEnvelope> for RecoveredInBlock {
88    type Recovered = Self;
89
90    fn into_parts(self) -> (TempoTxEnv, Self::Recovered) {
91        (self.to_tx_env(), self)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use alloy_consensus::{BlockHeader, Signed, TxLegacy};
99    use alloy_primitives::{B256, Bytes, Signature, TxKind, U256};
100    use alloy_rlp::{Encodable, bytes::BytesMut};
101    use rayon::iter::{IntoParallelIterator, ParallelIterator};
102    use reth_chainspec::EthChainSpec;
103    use reth_evm::{ConfigureEngineEvm, ConvertTx, ExecutableTxTuple};
104    use tempo_chainspec::{TempoChainSpec, spec::ANDANTINO};
105    use tempo_primitives::{
106        BlockBody, SubBlockMetadata, TempoHeader, transaction::envelope::TEMPO_SYSTEM_TX_SIGNATURE,
107    };
108
109    fn create_legacy_tx() -> TempoTxEnvelope {
110        let tx = TxLegacy {
111            chain_id: Some(1),
112            nonce: 0,
113            gas_price: 1,
114            gas_limit: 21000,
115            to: TxKind::Call(Address::repeat_byte(0x01)),
116            value: U256::ZERO,
117            input: Bytes::new(),
118        };
119        TempoTxEnvelope::Legacy(Signed::new_unhashed(tx, Signature::test_signature()))
120    }
121
122    fn create_subblock_metadata_tx(chain_id: u64, block_number: u64) -> TempoTxEnvelope {
123        let metadata: Vec<SubBlockMetadata> = vec![];
124        let mut input = BytesMut::new();
125        metadata.encode(&mut input);
126        input.extend_from_slice(&U256::from(block_number).to_be_bytes::<32>());
127
128        TempoTxEnvelope::Legacy(Signed::new_unhashed(
129            TxLegacy {
130                chain_id: Some(chain_id),
131                nonce: 0,
132                gas_price: 0,
133                gas_limit: 0,
134                to: TxKind::Call(Address::ZERO),
135                value: U256::ZERO,
136                input: input.freeze().into(),
137            },
138            TEMPO_SYSTEM_TX_SIGNATURE,
139        ))
140    }
141
142    fn create_test_block(transactions: Vec<TempoTxEnvelope>) -> Arc<SealedBlock<Block>> {
143        let header = TempoHeader {
144            inner: alloy_consensus::Header {
145                number: 1,
146                timestamp: 1000,
147                gas_limit: 30_000_000,
148                parent_beacon_block_root: Some(B256::ZERO),
149                ..Default::default()
150            },
151            general_gas_limit: 10_000_000,
152            timestamp_millis_part: 500,
153            shared_gas_limit: 3_000_000,
154        };
155
156        let body = BlockBody {
157            transactions,
158            ommers: vec![],
159            withdrawals: None,
160        };
161
162        let block = Block { header, body };
163        Arc::new(SealedBlock::seal_slow(block))
164    }
165
166    #[test]
167    fn test_tx_iterator_for_payload() {
168        let chainspec = Arc::new(TempoChainSpec::from_genesis(ANDANTINO.genesis().clone()));
169        let evm_config = TempoEvmConfig::new(chainspec.clone());
170
171        let tx1 = create_legacy_tx();
172        let tx2 = create_legacy_tx();
173        let system_tx = create_subblock_metadata_tx(chainspec.chain().id(), 1);
174
175        let block = create_test_block(vec![tx1, tx2, system_tx]);
176
177        let payload = TempoExecutionData {
178            block,
179            validator_set: None,
180        };
181
182        let result = evm_config.tx_iterator_for_payload(&payload);
183        assert!(result.is_ok());
184
185        let tuple = result.unwrap();
186        let (iter, recover_fn) = tuple.into_parts();
187        let items: Vec<_> = iter.into_par_iter().collect();
188
189        // Should have 3 transactions
190        assert_eq!(items.len(), 3);
191
192        // Test the recovery function works on all items
193        for item in items {
194            let recovered = recover_fn.convert(item);
195            assert!(recovered.is_ok());
196        }
197    }
198
199    #[test]
200    fn test_context_for_payload() {
201        let chainspec = Arc::new(TempoChainSpec::from_genesis(ANDANTINO.genesis().clone()));
202        let evm_config = TempoEvmConfig::new(chainspec.clone());
203
204        let system_tx = create_subblock_metadata_tx(chainspec.chain().id(), 1);
205        let block = create_test_block(vec![system_tx]);
206        let validator_set = Some(vec![B256::repeat_byte(0x01), B256::repeat_byte(0x02)]);
207
208        let payload = TempoExecutionData {
209            block,
210            validator_set: validator_set.clone(),
211        };
212
213        let result = evm_config.context_for_payload(&payload);
214        assert!(result.is_ok());
215
216        let context = result.unwrap();
217
218        // Verify context fields
219        assert_eq!(context.general_gas_limit, 10_000_000);
220        assert_eq!(context.shared_gas_limit, 3_000_000);
221        assert_eq!(context.validator_set, validator_set);
222        assert!(context.subblock_fee_recipients.is_empty());
223    }
224
225    #[test]
226    fn test_evm_env_for_payload() {
227        let chainspec = Arc::new(TempoChainSpec::from_genesis(ANDANTINO.genesis().clone()));
228        let evm_config = TempoEvmConfig::new(chainspec.clone());
229
230        let system_tx = create_subblock_metadata_tx(chainspec.chain().id(), 1);
231        let block = create_test_block(vec![system_tx]);
232
233        let payload = TempoExecutionData {
234            block: block.clone(),
235            validator_set: None,
236        };
237
238        let result = evm_config.evm_env_for_payload(&payload);
239        assert!(result.is_ok());
240
241        let evm_env = result.unwrap();
242
243        // Verify EVM environment fields
244        assert_eq!(evm_env.block_env.inner.number, U256::from(block.number()));
245        assert_eq!(
246            evm_env.block_env.inner.timestamp,
247            U256::from(block.timestamp())
248        );
249        assert_eq!(
250            evm_env.block_env.inner.gas_limit,
251            block.header().gas_limit()
252        );
253        assert_eq!(evm_env.block_env.timestamp_millis_part, 500);
254    }
255}