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#[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 assert_eq!(items.len(), 3);
220
221 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 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 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}