1pub(crate) use reth_engine_primitives::BigBlockData;
4
5use alloy_consensus::Header;
6use alloy_eips::Decodable2718;
7use alloy_primitives::{Bytes, U256};
8use alloy_rpc_types::engine::ExecutionData;
9use core::convert::Infallible;
10use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks};
11use reth_engine_primitives::ExecutionPayload;
12use reth_ethereum_forks::Hardforks;
13use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
14use reth_evm::{
15 precompile_cache::PrecompileCacheMap, BlockAssembler, BlockAssemblerInput, BlockExecutionError,
16 ConfigureEngineEvm, ConfigureEvm, EvmEnvFor, EvmFor, ExecutableTxIterator, ExecutionCtxFor,
17 NextBlockEnvAttributes, SpecFor,
18};
19use reth_evm_ethereum::{
20 EthBigBlockExecutorFactory, EthBigBlockPlan, EthBigBlockSegment, EthEvmConfig,
21 ExecutableRecoveredTx,
22};
23use reth_primitives_traits::{BlockTy, HeaderTy, SealedBlock, SealedHeader, SignedTransaction};
24use reth_storage_errors::any::AnyError;
25
26#[derive(Debug, Clone, Copy, Default)]
28pub struct BbBlockAssembler;
29
30impl<C> BlockAssembler<EthBigBlockExecutorFactory<C>> for BbBlockAssembler
31where
32 C: EthChainSpec<Header = Header> + EthereumHardforks + 'static,
33{
34 type Block = Block;
35
36 fn assemble_block(
37 &self,
38 _input: BlockAssemblerInput<'_, '_, EthBigBlockExecutorFactory<C>, Header>,
39 ) -> Result<Self::Block, BlockExecutionError> {
40 unreachable!("big-block execution is replay-only")
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct BbEvmConfig<C = ChainSpec> {
47 inner: EthEvmConfig<C>,
48 executor_factory: EthBigBlockExecutorFactory<C>,
49 block_assembler: BbBlockAssembler,
50}
51
52impl<C> BbEvmConfig<C> {
53 pub fn new(inner: EthEvmConfig<C>) -> Self
55 where
56 EthEvmConfig<C>: Clone,
57 EthBigBlockExecutorFactory<C>: Clone,
58 {
59 let executor_factory = EthBigBlockExecutorFactory::new(inner.executor_factory.clone());
60 Self { inner, executor_factory, block_assembler: BbBlockAssembler }
61 }
62}
63
64impl<C> ConfigureEvm for BbEvmConfig<C>
65where
66 C: EthChainSpec<Header = Header> + EthereumHardforks + Hardforks + 'static,
67{
68 type Primitives = EthPrimitives;
69 type Error = Infallible;
70 type NextBlockEnvCtx = NextBlockEnvAttributes;
71 type BlockExecutorFactory = EthBigBlockExecutorFactory<C>;
72 type BlockAssembler = BbBlockAssembler;
73
74 fn evm_with_precompile_cache<'a, DB>(
75 &self,
76 db: DB,
77 evm_env: EvmEnvFor<Self>,
78 cache_map: PrecompileCacheMap<SpecFor<Self>>,
79 metrics_enabled: bool,
80 ) -> EvmFor<'a, Self>
81 where
82 DB: evm2::evm::Database + 'a,
83 {
84 self.inner.evm_with_precompile_cache(db, evm_env, cache_map, metrics_enabled)
85 }
86
87 fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
88 &self.executor_factory
89 }
90
91 fn block_assembler(&self) -> &Self::BlockAssembler {
92 &self.block_assembler
93 }
94
95 fn with_jit_support_enabled(self, enabled: bool) -> Self {
96 Self::new(self.inner.with_jit_support_enabled(enabled))
97 }
98
99 fn jit_backend(&self) -> Option<&dyn reth_evm::JitBackend> {
100 self.inner.jit_backend()
101 }
102
103 fn evm_env(&self, header: &HeaderTy<Self::Primitives>) -> Result<EvmEnvFor<Self>, Self::Error> {
104 self.inner.evm_env(header)
105 }
106
107 fn next_evm_env(
108 &self,
109 parent: &HeaderTy<Self::Primitives>,
110 attributes: &Self::NextBlockEnvCtx,
111 ) -> Result<EvmEnvFor<Self>, Self::Error> {
112 self.inner.next_evm_env(parent, attributes)
113 }
114
115 fn context_for_block<'a>(
116 &self,
117 _block: &'a SealedBlock<BlockTy<Self::Primitives>>,
118 ) -> Result<ExecutionCtxFor<'a, Self>, Self::Error>
119 where
120 Self: 'a,
121 {
122 unreachable!("big blocks EVM should only be used from within the engine pipeline")
123 }
124
125 fn context_for_next_block(
126 &self,
127 _parent: &SealedHeader<HeaderTy<Self::Primitives>>,
128 _attributes: Self::NextBlockEnvCtx,
129 ) -> Result<ExecutionCtxFor<'_, Self>, Self::Error> {
130 unreachable!("big blocks EVM should only be used from within the engine pipeline")
131 }
132}
133
134impl<C> ConfigureEngineEvm<BigBlockData<ExecutionData>> for BbEvmConfig<C>
135where
136 C: EthChainSpec<Header = Header> + EthereumHardforks + Hardforks + 'static,
137{
138 fn evm_env_for_payload(
139 &self,
140 payload: &BigBlockData<ExecutionData>,
141 ) -> Result<EvmEnvFor<Self>, Self::Error> {
142 let first = payload.env_switches.first().expect("big-block payload has no segments");
143 let mut env = self.inner.evm_env_for_payload(first)?;
144 env.version.features.remove(evm2::EvmFeatures::BASE_FEE_CHECK);
147 env.block.gas_limit = U256::from(payload.gas_limit());
148 Ok(env)
149 }
150
151 fn context_for_payload<'a>(
152 &self,
153 payload: &'a BigBlockData<ExecutionData>,
154 ) -> Result<ExecutionCtxFor<'a, Self>, Self::Error> {
155 assert!(!payload.env_switches.is_empty(), "big-block payload has no segments");
156
157 let mut start_tx = 0;
158 let mut segments = Vec::with_capacity(payload.env_switches.len());
159 for data in &payload.env_switches {
160 let evm_env = self.inner.evm_env_for_payload(data)?;
161 let ctx = self.inner.context_for_payload(data)?;
162 segments.push(EthBigBlockSegment { start_tx, evm_env, ctx });
163 start_tx += data.payload.transactions().len();
164 }
165
166 Ok(EthBigBlockPlan::new(segments, payload.prior_block_hashes.clone(), start_tx))
167 }
168
169 fn tx_iterator_for_payload(
170 &self,
171 payload: &BigBlockData<ExecutionData>,
172 ) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
173 let transactions = payload
174 .env_switches
175 .iter()
176 .flat_map(|data| data.payload.transactions().iter().cloned())
177 .collect::<Vec<_>>();
178 let convert = |tx: Bytes| {
179 let tx = TransactionSigned::decode_2718_exact(tx.as_ref()).map_err(AnyError::new)?;
180 let signer = tx.try_recover().map_err(AnyError::new)?;
181 Ok::<_, AnyError>(ExecutableRecoveredTx::new(tx.with_signer(signer)))
182 };
183
184 Ok((transactions, convert))
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use alloy_consensus::TxLegacy;
192 use alloy_primitives::{Address, Signature, TxKind};
193 use alloy_rpc_types::engine::{ExecutionPayloadSidecar, ExecutionPayloadV1};
194 use evm2::evm::{EmptyDB, SystemTx};
195 use metrics_util::debugging::{DebugValue, DebuggingRecorder};
196 use reth_evm::EvmEnv;
197 use reth_evm_ethereum::EthEvmEnv;
198
199 #[test]
200 fn prewarm_accepts_later_segment_with_lower_base_fee() {
201 let segment = |base_fee| ExecutionData {
202 payload: ExecutionPayloadV1::from_block_unchecked(
203 Default::default(),
204 &Block {
205 header: Header {
206 number: 16_000_000,
207 timestamp: 1_670_000_000,
208 gas_limit: 30_000_000,
209 base_fee_per_gas: Some(base_fee),
210 ..Default::default()
211 },
212 body: Default::default(),
213 },
214 )
215 .into(),
216 sidecar: ExecutionPayloadSidecar::none(),
217 };
218 let payload = BigBlockData {
219 env_switches: vec![segment(100u64), segment(50u64)],
220 prior_block_hashes: Vec::new(),
221 block_number: 16_000_000,
222 merged_block_access_list: None,
223 };
224 let config = BbEvmConfig::new(EthEvmConfig::mainnet());
225 let env = config
226 .evm_env_for_payload(&payload)
227 .unwrap()
228 .with_nonce_check_disabled()
229 .with_balance_check_disabled();
230 let mut evm = config.evm_with_env(EmptyDB::default(), env);
231 let tx = TransactionSigned::new_unhashed(
232 TxLegacy {
233 gas_price: 50,
234 gas_limit: 21_000,
235 to: TxKind::Call(Address::with_last_byte(100)),
236 ..Default::default()
237 }
238 .into(),
239 Signature::new(U256::from(1), U256::from(1), false),
240 )
241 .with_signer(Address::with_last_byte(101))
242 .convert();
243 assert!(evm.transact(&tx).unwrap().discard().status);
244 assert_eq!(evm.block().gas_limit, U256::from(60_000_000));
245 assert!(config
246 .context_for_payload(&payload)
247 .unwrap()
248 .segments
249 .iter()
250 .all(|segment| segment.evm_env.version.feature(evm2::EvmFeatures::BASE_FEE_CHECK)));
251 }
252
253 #[test]
254 fn precompile_cache_is_opt_in_per_evm() {
255 let recorder = DebuggingRecorder::new();
256 metrics::with_local_recorder(&recorder, || {
257 let metrics_snapshot = || {
258 recorder
259 .snapshotter()
260 .snapshot()
261 .into_vec()
262 .into_iter()
263 .filter(|(key, ..)| key.key().name().starts_with("sync.caching.precompile"))
264 .collect::<Vec<_>>()
265 };
266 let config = BbEvmConfig::new(EthEvmConfig::mainnet());
267 let cache_map = PrecompileCacheMap::default();
268 let identity = Address::with_last_byte(4);
269 let sha256 = Address::with_last_byte(2);
270 let ecadd = Address::with_last_byte(6);
271 let call = |cache_metrics: Option<bool>, address, input: Bytes| {
272 let mut evm = match cache_metrics {
273 Some(metrics_enabled) => config.evm_with_precompile_cache(
274 EmptyDB::default(),
275 EthEvmEnv::default(),
276 cache_map.clone(),
277 metrics_enabled,
278 ),
279 None => config.evm_with_env(EmptyDB::default(), EthEvmEnv::default()),
280 };
281 evm.system_call(SystemTx::new(address, input)).unwrap().commit().status
282 };
283
284 let warm_input = Bytes::from_static(b"prewarmed");
286 assert!(call(None, identity, warm_input.clone()));
287 assert!(call(None, identity, warm_input.clone()));
288 assert!(metrics_snapshot().is_empty());
289
290 assert!(call(Some(false), identity, warm_input.clone()));
292 assert!(metrics_snapshot().is_empty());
293
294 assert!(call(Some(true), identity, warm_input.clone()));
295 let new_input = Bytes::from_static(b"uncached");
296 assert!(call(Some(true), identity, new_input.clone()));
297 assert!(call(Some(true), identity, new_input));
298 assert!(call(Some(true), sha256, warm_input.clone()));
299 assert!(!call(Some(true), ecadd, Bytes::from(vec![0xff; 128])));
300
301 let snapshot = metrics_snapshot();
302 let counter = |address, name| {
303 let label = format!("0x{address:02x}");
304 let (_, _, _, value) = snapshot
305 .iter()
306 .find(|(key, ..)| {
307 key.key().name() == name &&
308 key.key()
309 .labels()
310 .any(|l| l.key() == "address" && l.value() == label)
311 })
312 .expect("address-labelled metric registered");
313 value
314 };
315 assert_eq!(
316 counter(identity, "sync.caching.precompile_cache_hits"),
317 &DebugValue::Counter(2)
318 );
319 assert_eq!(
320 counter(identity, "sync.caching.precompile_cache_misses"),
321 &DebugValue::Counter(1)
322 );
323 assert_eq!(
324 counter(sha256, "sync.caching.precompile_cache_misses"),
325 &DebugValue::Counter(1)
326 );
327 assert_eq!(counter(ecadd, "sync.caching.precompile_errors"), &DebugValue::Counter(1));
328 assert!(matches!(
329 counter(identity, "sync.caching.precompile_cache_size"),
330 DebugValue::Gauge(_)
331 ));
332
333 assert!(call(None, identity, warm_input));
335 assert!(metrics_snapshot().iter().all(|(.., value)| {
337 value == &DebugValue::Counter(0) || value == &DebugValue::Gauge(0.0.into())
338 }));
339 });
340 }
341
342 #[cfg(feature = "jit")]
343 #[test]
344 fn jit_support_reaches_big_block_executor() {
345 let config = BbEvmConfig::new(EthEvmConfig::mainnet());
346 assert!(config.jit_backend().is_some());
347 assert!(!config.evm_factory().jit_support_enabled());
348
349 let config = config.with_jit_support();
350 assert!(config.evm_factory().jit_support_enabled());
351 assert!(!config.with_jit_support_enabled(false).evm_factory().jit_support_enabled());
352 }
353}