1use super::BlockId;
16use crate::{
17 beacon::BeaconCommit,
18 config::ChainSpec,
19 ethereum::EthEvmFactory,
20 history::HistoryCommit,
21 host::{
22 db::{ProofDb, ProviderConfig, ProviderDb},
23 BlockNumberOrTag, EthHostEvmEnv, HostCommit, HostEvmEnv,
24 },
25 CommitmentVersion, EvmBlockHeader, EvmEnv, EvmFactory,
26};
27use alloy::{
28 network::{primitives::HeaderResponse, BlockResponse, Ethereum, Network},
29 providers::{Provider, ProviderBuilder, RootProvider},
30};
31use alloy_primitives::{BlockHash, BlockNumber, Sealable, Sealed, B256};
32use anyhow::{anyhow, ensure, Context, Result};
33use std::{fmt::Display, marker::PhantomData};
34use url::Url;
35
36impl<F: EvmFactory> EvmEnv<(), F, ()> {
37 pub fn builder() -> EvmEnvBuilder<(), F, (), ()> {
51 EvmEnvBuilder {
52 provider: (),
53 provider_config: ProviderConfig::default(),
54 block: BlockId::default(),
55 chain_spec: (),
56 beacon_config: (),
57 phantom: PhantomData,
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
73pub struct EvmEnvBuilder<P, F, S, B> {
74 provider: P,
75 provider_config: ProviderConfig,
76 block: BlockId,
77 chain_spec: S,
78 beacon_config: B,
79 phantom: PhantomData<F>,
80}
81
82impl<S> EvmEnvBuilder<(), EthEvmFactory, S, ()> {
83 pub fn rpc(self, url: Url) -> EvmEnvBuilder<RootProvider<Ethereum>, EthEvmFactory, S, ()> {
85 self.provider(ProviderBuilder::default().connect_http(url))
86 }
87}
88
89impl<F: EvmFactory, S> EvmEnvBuilder<(), F, S, ()> {
90 pub fn provider<N, P>(self, provider: P) -> EvmEnvBuilder<P, F, S, ()>
92 where
93 N: Network,
94 P: Provider<N>,
95 F::Header: TryFrom<<N as Network>::HeaderResponse>,
96 <F::Header as TryFrom<<N as Network>::HeaderResponse>>::Error: Display,
97 {
98 EvmEnvBuilder {
99 provider,
100 provider_config: self.provider_config,
101 block: self.block,
102 chain_spec: self.chain_spec,
103 beacon_config: self.beacon_config,
104 phantom: self.phantom,
105 }
106 }
107}
108
109impl<P, F: EvmFactory, B> EvmEnvBuilder<P, F, (), B> {
110 pub fn chain_spec(
112 self,
113 chain_spec: &ChainSpec<F::Spec>,
114 ) -> EvmEnvBuilder<P, F, &ChainSpec<F::Spec>, B> {
115 EvmEnvBuilder {
116 provider: self.provider,
117 provider_config: self.provider_config,
118 block: self.block,
119 chain_spec,
120 beacon_config: self.beacon_config,
121 phantom: self.phantom,
122 }
123 }
124}
125
126#[derive(Clone, Debug)]
128pub struct Beacon {
129 url: Url,
130 commitment_version: CommitmentVersion,
131}
132
133impl<P, S> EvmEnvBuilder<P, EthEvmFactory, S, ()> {
134 pub fn beacon_api(self, url: Url) -> EvmEnvBuilder<P, EthEvmFactory, S, Beacon> {
139 EvmEnvBuilder {
140 provider: self.provider,
141 provider_config: self.provider_config,
142 block: self.block,
143 chain_spec: self.chain_spec,
144 beacon_config: Beacon {
145 url,
146 commitment_version: CommitmentVersion::Beacon,
147 },
148 phantom: self.phantom,
149 }
150 }
151}
152
153impl<P, F, S, B> EvmEnvBuilder<P, F, S, B> {
154 pub fn block_number(self, number: u64) -> Self {
156 self.block_number_or_tag(BlockNumberOrTag::Number(number))
157 }
158
159 pub fn block_number_or_tag(mut self, block: BlockNumberOrTag) -> Self {
162 self.block = BlockId::Number(block);
163 self
164 }
165
166 pub fn block_hash(mut self, hash: B256) -> Self {
168 self.block = BlockId::Hash(hash);
169 self
170 }
171
172 pub fn eip1186_proof_chunk_size(mut self, chunk_size: usize) -> Self {
177 assert_ne!(chunk_size, 0, "chunk size must be non-zero");
178 self.provider_config.eip1186_proof_chunk_size = chunk_size;
179 self
180 }
181
182 async fn get_header<N>(&self, block: Option<BlockId>) -> Result<Sealed<F::Header>>
186 where
187 F: EvmFactory,
188 N: Network,
189 P: Provider<N>,
190 F::Header: TryFrom<<N as Network>::HeaderResponse>,
191 <F::Header as TryFrom<<N as Network>::HeaderResponse>>::Error: Display,
192 {
193 let block = block.unwrap_or(self.block);
194 let block = block.into_rpc_type(&self.provider).await?;
195
196 let rpc_block = self
197 .provider
198 .get_block(block)
199 .await
200 .context("eth_getBlock1 failed")?
201 .with_context(|| format!("block {block} not found"))?;
202
203 let rpc_header = rpc_block.header().clone();
204 let header: F::Header = rpc_header
205 .try_into()
206 .map_err(|err| anyhow!("header invalid: {}", err))?;
207 let header = header.seal_slow();
208 ensure!(
209 header.seal() == rpc_block.header().hash(),
210 "computed block hash does not match the hash returned by the API"
211 );
212
213 Ok(header)
214 }
215}
216
217impl<P, F: EvmFactory> EvmEnvBuilder<P, F, &ChainSpec<F::Spec>, ()> {
218 pub async fn build<N>(self) -> Result<HostEvmEnv<ProviderDb<N, P>, F, ()>>
220 where
221 N: Network,
222 P: Provider<N>,
223 F::Header: TryFrom<<N as Network>::HeaderResponse>,
224 <F::Header as TryFrom<<N as Network>::HeaderResponse>>::Error: Display,
225 {
226 let header = self.get_header(None).await?;
227 log::info!(
228 "Environment initialized with block {} ({})",
229 header.number(),
230 header.seal()
231 );
232
233 let db = ProofDb::new(ProviderDb::new(
234 self.provider,
235 self.provider_config,
236 header.seal(),
237 ));
238 let commit = HostCommit {
239 inner: (),
240 config_id: self.chain_spec.digest(),
241 };
242
243 Ok(EvmEnv::new(db, self.chain_spec, header, commit))
244 }
245}
246
247#[derive(Clone, Debug)]
249pub struct History {
250 beacon_config: Beacon,
251 commitment_block: BlockId,
252}
253
254impl<P, S> EvmEnvBuilder<P, EthEvmFactory, S, Beacon> {
255 pub fn consensus_commitment(mut self) -> Self {
287 self.beacon_config.commitment_version = CommitmentVersion::Consensus;
288 self
289 }
290
291 pub fn commitment_block_hash(
320 self,
321 hash: BlockHash,
322 ) -> EvmEnvBuilder<P, EthEvmFactory, S, History> {
323 self.commitment_block(BlockId::Hash(hash))
324 }
325
326 pub fn commitment_block_number_or_tag(
330 self,
331 block: BlockNumberOrTag,
332 ) -> EvmEnvBuilder<P, EthEvmFactory, S, History> {
333 self.commitment_block(BlockId::Number(block))
334 }
335
336 pub fn commitment_block_number(
340 self,
341 number: BlockNumber,
342 ) -> EvmEnvBuilder<P, EthEvmFactory, S, History> {
343 self.commitment_block_number_or_tag(BlockNumberOrTag::Number(number))
344 }
345
346 fn commitment_block(self, block: BlockId) -> EvmEnvBuilder<P, EthEvmFactory, S, History> {
347 EvmEnvBuilder {
348 provider: self.provider,
349 provider_config: self.provider_config,
350 block: self.block,
351 chain_spec: self.chain_spec,
352 beacon_config: History {
353 beacon_config: self.beacon_config,
354 commitment_block: block,
355 },
356 phantom: Default::default(),
357 }
358 }
359}
360
361impl<P> EvmEnvBuilder<P, EthEvmFactory, &ChainSpec<<EthEvmFactory as EvmFactory>::Spec>, Beacon> {
362 pub async fn build(self) -> Result<EthHostEvmEnv<ProviderDb<Ethereum, P>, BeaconCommit>>
364 where
365 P: Provider<Ethereum>,
366 {
367 let header = self.get_header(None).await?;
368 log::info!(
369 "Environment initialized with block {} ({})",
370 header.number(),
371 header.seal()
372 );
373
374 let beacon_url = self.beacon_config.url;
375 let version = self.beacon_config.commitment_version;
376 let commit = HostCommit {
377 inner: BeaconCommit::from_header(&header, version, &self.provider, beacon_url).await?,
378 config_id: self.chain_spec.digest(),
379 };
380 let db = ProofDb::new(ProviderDb::new(
381 self.provider,
382 self.provider_config,
383 header.seal(),
384 ));
385
386 Ok(EvmEnv::new(db, self.chain_spec, header, commit))
387 }
388}
389
390impl<P> EvmEnvBuilder<P, EthEvmFactory, &ChainSpec<<EthEvmFactory as EvmFactory>::Spec>, History> {
391 pub fn consensus_commitment(mut self) -> Self {
395 self.beacon_config.beacon_config.commitment_version = CommitmentVersion::Consensus;
396 self
397 }
398 pub async fn build(self) -> Result<EthHostEvmEnv<ProviderDb<Ethereum, P>, HistoryCommit>>
401 where
402 P: Provider<Ethereum>,
403 {
404 let evm_header = self.get_header(None).await?;
405 let commitment_header = self
406 .get_header(Some(self.beacon_config.commitment_block))
407 .await?;
408 ensure!(
409 evm_header.number() < commitment_header.number(),
410 "EVM execution block not before commitment block"
411 );
412
413 log::info!(
414 "Environment initialized with block {} ({})",
415 evm_header.number(),
416 evm_header.seal()
417 );
418
419 let beacon_url = self.beacon_config.beacon_config.url;
420 let commitment_version = self.beacon_config.beacon_config.commitment_version;
421 let history_commit = HistoryCommit::from_headers(
422 &evm_header,
423 &commitment_header,
424 commitment_version,
425 &self.provider,
426 beacon_url,
427 )
428 .await?;
429 let commit = HostCommit {
430 inner: history_commit,
431 config_id: self.chain_spec.digest(),
432 };
433 let db = ProofDb::new(ProviderDb::new(
434 self.provider,
435 self.provider_config,
436 evm_header.seal(),
437 ));
438
439 Ok(EvmEnv::new(db, self.chain_spec, evm_header, commit))
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::{
447 ethereum::{EthEvmEnv, ETH_MAINNET_CHAIN_SPEC},
448 test_utils::{get_cl_url, get_el_url},
449 BlockHeaderCommit, Commitment, CommitmentVersion,
450 };
451 use test_log::test;
452
453 #[test(tokio::test)]
454 #[cfg_attr(
455 any(not(feature = "rpc-tests"), no_auth),
456 ignore = "RPC tests are disabled"
457 )]
458 async fn build_block_env() {
459 let builder = EthEvmEnv::builder()
460 .rpc(get_el_url())
461 .chain_spec(Ð_MAINNET_CHAIN_SPEC);
462 builder.clone().build().await.unwrap();
464 }
465
466 #[test(tokio::test)]
467 #[cfg_attr(
468 any(not(feature = "rpc-tests"), no_auth),
469 ignore = "RPC tests are disabled"
470 )]
471 async fn build_beacon_env() {
472 let provider = ProviderBuilder::default().connect_http(get_el_url());
473
474 let builder = EthEvmEnv::builder()
475 .provider(&provider)
476 .beacon_api(get_cl_url())
477 .block_number_or_tag(BlockNumberOrTag::Parent)
478 .chain_spec(Ð_MAINNET_CHAIN_SPEC);
479 let env = builder.clone().build().await.unwrap();
480 let commit = env.commit.inner.commit(&env.header, env.commit.config_id);
481
482 let child_block = provider
484 .get_block_by_number((env.header.number() + 1).into())
485 .await
486 .unwrap();
487 let header = child_block.unwrap().header;
488 assert_eq!(
489 commit,
490 Commitment::new(
491 CommitmentVersion::Beacon as u16,
492 header.timestamp,
493 header.parent_beacon_block_root.unwrap(),
494 ETH_MAINNET_CHAIN_SPEC.digest(),
495 )
496 );
497 }
498
499 #[test(tokio::test)]
500 #[cfg_attr(
501 any(not(feature = "rpc-tests"), no_auth),
502 ignore = "RPC tests are disabled"
503 )]
504 async fn build_history_env() {
505 let provider = ProviderBuilder::default().connect_http(get_el_url());
506
507 let latest = provider.get_block_number().await.unwrap();
509 let builder = EthEvmEnv::builder()
510 .provider(&provider)
511 .block_number_or_tag(BlockNumberOrTag::Number(latest - 8191))
512 .beacon_api(get_cl_url())
513 .commitment_block_number(latest - 1)
514 .chain_spec(Ð_MAINNET_CHAIN_SPEC);
515 let env = builder.clone().build().await.unwrap();
516 let commit = env.commit.inner.commit(&env.header, env.commit.config_id);
517
518 let child_block = provider.get_block_by_number(latest.into()).await.unwrap();
520 let header = child_block.unwrap().header;
521 assert_eq!(
522 commit,
523 Commitment::new(
524 CommitmentVersion::Beacon as u16,
525 header.timestamp,
526 header.parent_beacon_block_root.unwrap(),
527 ETH_MAINNET_CHAIN_SPEC.digest(),
528 )
529 );
530 }
531}