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