1use std::{sync::Arc, time::Duration};
2
3use alloy::{
4 network::Ethereum,
5 providers::{Provider, ProviderBuilder},
6};
7use anyhow::{anyhow, bail, Result as AnyhowResult};
8use backoff::ExponentialBackoffBuilder;
9use dyn_clone::DynClone;
10
11use crate::{
12 clients::{
13 beacon::{BeaconClient, CommonBeaconClient, Config as BeaconClientConfig},
14 blobscan::{BlobscanClient, CommonBlobscanClient, Config as BlobscanClientConfig},
15 },
16 network::{Network, NetworkName},
17};
18
19pub struct SyncingSettings {
20 pub concurrency: u32,
21 pub checkpoint_size: u32,
22 pub disable_checkpoints: bool,
23}
24
25pub trait CommonContext: Send + Sync + DynClone {
29 fn beacon_client(&self) -> &dyn CommonBeaconClient;
30 fn blobscan_client(&self) -> &dyn CommonBlobscanClient;
31 fn network(&self) -> &Network;
32 fn provider(&self) -> &dyn Provider<Ethereum>;
33 fn syncing_settings(&self) -> &SyncingSettings;
34}
35
36dyn_clone::clone_trait_object!(CommonContext);
37struct ContextRef {
40 pub network: Network,
41 pub beacon_client: Box<dyn CommonBeaconClient>,
42 pub blobscan_client: Box<dyn CommonBlobscanClient>,
43 pub provider: Box<dyn Provider<Ethereum>>,
44 pub syncing_settings: SyncingSettings,
45}
46
47#[derive(Clone)]
48pub struct Context {
49 inner: Arc<ContextRef>,
50}
51
52pub struct ContextConfig {
53 pub network: Network,
54 pub beacon_api_base_url: String,
55 pub blobscan_api_base_url: String,
56 pub blobscan_secret_key: String,
57 pub execution_node_base_url: String,
58 pub syncing_settings: SyncingSettings,
59}
60
61impl Context {
62 pub async fn try_new(config: ContextConfig) -> AnyhowResult<Self> {
63 let exp_backoff = Some(ExponentialBackoffBuilder::default().build());
64 let client = reqwest::Client::builder()
65 .timeout(Duration::from_secs(16))
66 .build()?;
67 let provider = ProviderBuilder::new()
68 .network::<Ethereum>()
69 .connect_http(config.execution_node_base_url.parse()?);
70
71 let ctx = Self {
72 inner: Arc::new(ContextRef {
73 network: config.network,
74 syncing_settings: config.syncing_settings,
75 blobscan_client: Box::new(BlobscanClient::try_with_client(
76 client.clone(),
77 BlobscanClientConfig {
78 base_url: config.blobscan_api_base_url.clone(),
79 secret_key: config.blobscan_secret_key.clone(),
80 exp_backoff: exp_backoff.clone(),
81 },
82 )?),
83 beacon_client: Box::new(BeaconClient::try_with_client(
84 client,
85 BeaconClientConfig {
86 base_url: config.beacon_api_base_url.clone(),
87 exp_backoff,
88 },
89 )?),
90 provider: Box::new(provider),
92 }),
93 };
94
95 ctx.validate_clients_consistency().await?;
96
97 Ok(ctx)
98 }
99
100 async fn validate_clients_consistency(&self) -> AnyhowResult<()> {
101 let execution_chain_id = self.provider().get_chain_id().await?;
102 let consensus_spec = self.beacon_client().get_spec().await?;
103 let network = self.network();
104
105 match consensus_spec {
106 Some(spec) => {
107 let deposit_network_id = spec.deposit_network_id;
108 if deposit_network_id != execution_chain_id {
109 bail!(
110 "Execution and Consensus clients mismatch: \n consensus deposit_network_id = {deposit_network_id}, execution chain_id = {execution_chain_id}"
111 );
112 }
113
114 if let NetworkName::Preset(p) = network.name {
115 if network.chain_id != execution_chain_id {
116 bail!("Environment network mismatch for '{p}': expected chain_id={}, got {} from execution client", network.chain_id, execution_chain_id);
117 }
118 }
119 }
120 None => {
121 return Err(anyhow!("No consensus spec found"));
122 }
123 };
124
125 Ok(())
126 }
127}
128
129impl CommonContext for Context {
130 fn beacon_client(&self) -> &dyn CommonBeaconClient {
131 self.inner.beacon_client.as_ref()
132 }
133
134 fn blobscan_client(&self) -> &dyn CommonBlobscanClient {
135 self.inner.blobscan_client.as_ref()
136 }
137
138 fn provider(&self) -> &dyn Provider<Ethereum> {
139 self.inner.provider.as_ref()
140 }
141
142 fn syncing_settings(&self) -> &SyncingSettings {
143 &self.inner.syncing_settings
144 }
145
146 fn network(&self) -> &Network {
147 &self.inner.network
148 }
149}
150
151