1use super::BlockId;
16use crate::{
17 beacon::{host::client::BeaconClient, 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::SpecId>,
114 ) -> EvmEnvBuilder<P, F, &ChainSpec<F::SpecId>, 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::SpecId>, ()> {
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::debug!(
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>::SpecId>, 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::debug!(
369 "Environment initialized with block {} ({})",
370 header.number(),
371 header.seal()
372 );
373
374 let beacon_client = BeaconClient::new(self.beacon_config.url)
375 .map_err(|e| anyhow::anyhow!("invalid beacon URL: {e}"))?;
376 let version = self.beacon_config.commitment_version;
377 let commit = HostCommit {
378 inner: BeaconCommit::from_header(&header, version, &self.provider, &beacon_client)
379 .await?,
380 config_id: self.chain_spec.digest(),
381 };
382 let db = ProofDb::new(ProviderDb::new(
383 self.provider,
384 self.provider_config,
385 header.seal(),
386 ));
387
388 Ok(EvmEnv::new(db, self.chain_spec, header, commit))
389 }
390}
391
392impl<P>
393 EvmEnvBuilder<P, EthEvmFactory, &ChainSpec<<EthEvmFactory as EvmFactory>::SpecId>, History>
394{
395 pub fn consensus_commitment(mut self) -> Self {
399 self.beacon_config.beacon_config.commitment_version = CommitmentVersion::Consensus;
400 self
401 }
402 pub async fn build(self) -> Result<EthHostEvmEnv<ProviderDb<Ethereum, P>, HistoryCommit>>
405 where
406 P: Provider<Ethereum>,
407 {
408 let evm_header = self.get_header(None).await?;
409 let commitment_header = self
410 .get_header(Some(self.beacon_config.commitment_block))
411 .await?;
412 ensure!(
413 evm_header.number() < commitment_header.number(),
414 "EVM execution block not before commitment block"
415 );
416
417 log::debug!(
418 "Environment initialized with block {} ({})",
419 evm_header.number(),
420 evm_header.seal()
421 );
422
423 let beacon_url = self.beacon_config.beacon_config.url;
424 let commitment_version = self.beacon_config.beacon_config.commitment_version;
425 let history_commit = HistoryCommit::from_headers(
426 &evm_header,
427 &commitment_header,
428 commitment_version,
429 &self.provider,
430 beacon_url,
431 )
432 .await?;
433 let commit = HostCommit {
434 inner: history_commit,
435 config_id: self.chain_spec.digest(),
436 };
437 let db = ProofDb::new(ProviderDb::new(
438 self.provider,
439 self.provider_config,
440 evm_header.seal(),
441 ));
442
443 Ok(EvmEnv::new(db, self.chain_spec, evm_header, commit))
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::{
451 ethereum::{EthEvmEnv, ETH_MAINNET_CHAIN_SPEC},
452 test_utils::{get_cl_url, get_el_url},
453 BlockHeaderCommit, Commitment, CommitmentVersion,
454 };
455 use test_log::test;
456
457 #[test(tokio::test)]
458 #[cfg_attr(
459 any(not(feature = "rpc-tests"), no_auth),
460 ignore = "RPC tests are disabled"
461 )]
462 async fn build_block_env() {
463 let builder = EthEvmEnv::builder()
464 .rpc(get_el_url())
465 .chain_spec(Ð_MAINNET_CHAIN_SPEC);
466 builder.clone().build().await.unwrap();
468 }
469
470 #[test(tokio::test)]
471 #[cfg_attr(
472 any(not(feature = "rpc-tests"), no_auth),
473 ignore = "RPC tests are disabled"
474 )]
475 async fn build_beacon_env() {
476 let provider = ProviderBuilder::default().connect_http(get_el_url());
477
478 let builder = EthEvmEnv::builder()
479 .provider(&provider)
480 .beacon_api(get_cl_url())
481 .block_number_or_tag(BlockNumberOrTag::Parent)
482 .chain_spec(Ð_MAINNET_CHAIN_SPEC);
483 let env = builder.clone().build().await.unwrap();
484 let commit = env.commit.inner.commit(&env.header, env.commit.config_id);
485
486 let child_block = provider
488 .get_block_by_number((env.header.number() + 1).into())
489 .await
490 .unwrap();
491 let header = child_block.unwrap().header;
492 assert_eq!(
493 commit,
494 Commitment::new(
495 CommitmentVersion::Beacon as u16,
496 header.timestamp,
497 header.parent_beacon_block_root.unwrap(),
498 ETH_MAINNET_CHAIN_SPEC.digest(),
499 )
500 );
501 }
502
503 #[test(tokio::test)]
504 #[cfg_attr(
505 any(not(feature = "rpc-tests"), no_auth),
506 ignore = "RPC tests are disabled"
507 )]
508 async fn build_history_env() {
509 let provider = ProviderBuilder::default().connect_http(get_el_url());
510
511 let latest = provider.get_block_number().await.unwrap();
513 let builder = EthEvmEnv::builder()
514 .provider(&provider)
515 .block_number_or_tag(BlockNumberOrTag::Number(latest - 8191))
516 .beacon_api(get_cl_url())
517 .commitment_block_number(latest - 1)
518 .chain_spec(Ð_MAINNET_CHAIN_SPEC);
519 let env = builder.clone().build().await.unwrap();
520 let commit = env.commit.inner.commit(&env.header, env.commit.config_id);
521
522 let child_block = provider.get_block_by_number(latest.into()).await.unwrap();
524 let header = child_block.unwrap().header;
525 assert_eq!(
526 commit,
527 Commitment::new(
528 CommitmentVersion::Beacon as u16,
529 header.timestamp,
530 header.parent_beacon_block_root.unwrap(),
531 ETH_MAINNET_CHAIN_SPEC.digest(),
532 )
533 );
534 }
535}