From 8566cd4d57d9f8c5b296ff8149627afa14711a99 Mon Sep 17 00:00:00 2001 From: Nick Gheorghita Date: Thu, 18 Apr 2024 14:51:04 -0400 Subject: [PATCH] feat(bridge): add fallback providers (#1260) --- Cargo.lock | 2 + Cargo.toml | 2 +- ethportal-peertest/Cargo.toml | 2 + ethportal-peertest/src/scenarios/bridge.rs | 10 +- portal-bridge/Cargo.toml | 2 +- portal-bridge/src/api/consensus.rs | 74 +++++---- portal-bridge/src/api/execution.rs | 169 ++++++++------------- portal-bridge/src/cli.rs | 139 +++++++++++------ portal-bridge/src/lib.rs | 25 +-- portal-bridge/src/main.rs | 14 +- src/bin/test_providers.rs | 51 ++++++- 11 files changed, 261 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9e71299c..63cd0351a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2659,6 +2659,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "surf", "tempfile", "tokio", "tracing", @@ -2671,6 +2672,7 @@ dependencies = [ "trin-validation", "uds_windows", "ureq", + "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9a4371cf0..6b87ab423 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ reth-ipc = { tag = "v0.2.0-beta.5", git = "https://github.com/paradigmxyz/reth.g rpc = { path = "rpc"} serde_json = {version = "1.0.89", features = ["preserve_order"]} sha3 = "0.9.1" -surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls", "middleware-logger", "encoding"] } # we use rustils because OpenSSL cause issues compiling on aarch64 +surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls", "middleware-logger", "encoding"] } # we use rustls because OpenSSL cause issues compiling on aarch64 tempfile = "3.3.0" tokio = { version = "1.14.0", features = ["full"] } tracing = "0.1.36" diff --git a/ethportal-peertest/Cargo.toml b/ethportal-peertest/Cargo.toml index 639a64a30..3b398e371 100644 --- a/ethportal-peertest/Cargo.toml +++ b/ethportal-peertest/Cargo.toml @@ -26,6 +26,7 @@ rpc = { path = "../rpc" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.89" serde_yaml = "0.9" +surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls", "middleware-logger", "encoding"] } # we use rustls because OpenSSL cause issues compiling on aarch64 tempfile = "3.3.0" tokio = {version = "1.14.0", features = ["full"]} tracing = "0.1.36" @@ -38,6 +39,7 @@ trin-state = { path = "../trin-state" } trin-utils = { path = "../trin-utils" } trin-validation = { path = "../trin-validation" } ureq = { version = "2.5.0", features = ["json"] } +url = "2.3.1" [target.'cfg(windows)'.dependencies] uds_windows = "1.0.1" diff --git a/ethportal-peertest/src/scenarios/bridge.rs b/ethportal-peertest/src/scenarios/bridge.rs index ae78eaa74..f716021b8 100644 --- a/ethportal-peertest/src/scenarios/bridge.rs +++ b/ethportal-peertest/src/scenarios/bridge.rs @@ -6,14 +6,15 @@ use ethportal_api::{jsonrpsee::http_client::HttpClient, BeaconContentKey, Beacon use portal_bridge::{ api::{consensus::ConsensusApi, execution::ExecutionApi}, bridge::{beacon::BeaconBridge, history::HistoryBridge}, - cli::Provider, constants::DEFAULT_GOSSIP_LIMIT, types::mode::BridgeMode, }; use serde_json::Value; use std::sync::Arc; +use surf::{Client, Config}; use tokio::time::{sleep, Duration}; use trin_validation::{accumulator::MasterAccumulator, oracle::HeaderOracle}; +use url::Url; pub async fn test_history_bridge(peertest: &Peertest, target: &HttpClient) { let master_acc = MasterAccumulator::default(); @@ -21,9 +22,12 @@ pub async fn test_history_bridge(peertest: &Peertest, target: &HttpClient) { let portal_clients = vec![target.clone()]; let epoch_acc_path = "validation_assets/epoch_acc.bin".into(); let mode = BridgeMode::Test("./test_assets/portalnet/bridge_data.json".into()); - let execution_api = ExecutionApi::new(Provider::Test, mode.clone()) - .await + let client: Client = Config::new() + // url doesn't matter, we're not making any requests + .set_base_url(Url::parse("http://www.null.com").unwrap()) + .try_into() .unwrap(); + let execution_api = ExecutionApi::new(client.clone(), client).await.unwrap(); // Wait for bootnode to start sleep(Duration::from_secs(1)).await; let bridge = HistoryBridge::new( diff --git a/portal-bridge/Cargo.toml b/portal-bridge/Cargo.toml index 2f761c63f..abc131c52 100644 --- a/portal-bridge/Cargo.toml +++ b/portal-bridge/Cargo.toml @@ -39,7 +39,7 @@ serde_json = "1.0.89" serde_yaml = "0.9" snap = "1.1.1" ssz_types = { git = "https://github.com/KolbyML/ssz_types.git", rev = "2a5922de75f00746890bf4ea9ad663c9d5d58efe" } -surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls", "middleware-logger", "encoding"] } # we use rustils because OpenSSL cause issues compiling on aarch64 +surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls", "middleware-logger", "encoding"] } # we use rustls because OpenSSL cause issues compiling on aarch64 tokio = { version = "1.14.0", features = ["full"] } tracing = "0.1.36" tracing-subscriber = "0.3.15" diff --git a/portal-bridge/src/api/consensus.rs b/portal-bridge/src/api/consensus.rs index 0527cb7f9..a95499191 100644 --- a/portal-bridge/src/api/consensus.rs +++ b/portal-bridge/src/api/consensus.rs @@ -1,57 +1,40 @@ use std::fmt::Display; use anyhow::anyhow; -use surf::{Client, Config}; -use tracing::debug; -use url::Url; - -use crate::{ - cli::Provider, constants::HTTP_REQUEST_TIMEOUT, BASE_CL_ENDPOINT, PANDAOPS_CLIENT_ID, - PANDAOPS_CLIENT_SECRET, -}; +use surf::Client; +use tracing::{debug, warn}; /// Implements endpoints from the Beacon API to access data from the consensus layer. #[derive(Clone, Debug, Default)] pub struct ConsensusApi { client: Client, + fallback_client: Client, } impl ConsensusApi { - pub async fn new(provider: Provider) -> Result { - let client: Client = match provider { - Provider::PandaOps => { - let base_cl_endpoint = Url::parse(&BASE_CL_ENDPOINT) - .expect("to be able to parse static base cl endpoint url"); - Config::new() - .add_header("CF-Access-Client-Id", PANDAOPS_CLIENT_ID.to_string())? - .add_header( - "CF-Access-Client-Secret", - PANDAOPS_CLIENT_SECRET.to_string(), - )? - .add_header("Content-Type", "application/json")? - .set_base_url(base_cl_endpoint) - .set_timeout(Some(HTTP_REQUEST_TIMEOUT)) - .try_into()? - } - Provider::Url(url) => Config::new() - .add_header("Content-Type", "application/json")? - .set_base_url(url) - .set_timeout(Some(HTTP_REQUEST_TIMEOUT)) - .try_into()?, - Provider::Test => { - return Err(surf::Error::from(anyhow!( - "Invalid provider, test mode is not supported for ConsensusApi" - ))) - } - }; + pub async fn new(client: Client, fallback_client: Client) -> Result { debug!( - "Starting ConsensusApi with provider at url: {:?}", - client.config().base_url + "Starting ConsensusApi with primary provider: {} and fallback provider: {}", + client + .config() + .base_url + .as_ref() + .expect("to have base url set") + .as_str(), + fallback_client + .config() + .base_url + .as_ref() + .expect("to have base url set") + .as_str() ); check_provider(&client) .await .map_err(|err| anyhow!("Check CL provider failed. Provider may be offline: {err:?}"))?; - Ok(Self { client }) + Ok(Self { + client, + fallback_client, + }) } /// Requests the `LightClientBootstrap` structure corresponding to a given post-Altair beacon @@ -99,8 +82,19 @@ impl ConsensusApi { /// Make a request to the cl provider. async fn request(&self, endpoint: String) -> anyhow::Result { - let result = self.client.get(endpoint).recv_string().await; - result.map_err(|err| anyhow!("Unable to request consensus data from provider: {err:?}")) + match self.client.get(&endpoint).recv_string().await { + Ok(response) => Ok(response), + Err(err) => { + warn!("Error requesting consensus data from provider, retrying with fallback provider: {err:?}"); + self.fallback_client + .get(endpoint) + .recv_string() + .await + .map_err(|err| { + anyhow!("Unable to request consensus data from fallback provider: {err:?}") + }) + } + } } } diff --git a/portal-bridge/src/api/execution.rs b/portal-bridge/src/api/execution.rs index 84d47bb54..4945c4c9b 100644 --- a/portal-bridge/src/api/execution.rs +++ b/portal-bridge/src/api/execution.rs @@ -4,20 +4,10 @@ use alloy_primitives::B256; use anyhow::{anyhow, bail}; use futures::future::join_all; use serde_json::{json, Value}; -use surf::{ - middleware::{Middleware, Next}, - Body, Client, Config, Request, Response, -}; -use tokio::time::{sleep, Duration}; -use tracing::{debug, warn}; -use url::Url; +use surf::Client; +use tracing::{debug, error, warn}; -use crate::{ - cli::Provider, - constants::HTTP_REQUEST_TIMEOUT, - types::{full_header::FullHeader, mode::BridgeMode}, - BASE_EL_ARCHIVE_ENDPOINT, BASE_EL_ENDPOINT, PANDAOPS_CLIENT_ID, PANDAOPS_CLIENT_SECRET, -}; +use crate::types::full_header::FullHeader; use ethportal_api::{ types::{ execution::{ @@ -45,50 +35,37 @@ const BATCH_LIMIT: usize = 100; #[derive(Clone, Debug)] pub struct ExecutionApi { pub client: Client, + pub fallback_client: Client, pub master_acc: MasterAccumulator, } impl ExecutionApi { - pub async fn new(provider: Provider, mode: BridgeMode) -> Result { - let client: Client = match &provider { - Provider::PandaOps => { - let endpoint = match mode { - BridgeMode::Backfill(_) => BASE_EL_ARCHIVE_ENDPOINT.to_string(), - _ => BASE_EL_ENDPOINT.to_string(), - }; - let base_el_endpoint = - Url::parse(&endpoint).expect("to be able to parse static base el endpoint url"); - Config::new() - .add_header("Content-Type", "application/json")? - .add_header("CF-Access-Client-Id", PANDAOPS_CLIENT_ID.to_string())? - .add_header( - "CF-Access-Client-Secret", - PANDAOPS_CLIENT_SECRET.to_string(), - )? - .set_base_url(base_el_endpoint) - .set_timeout(Some(HTTP_REQUEST_TIMEOUT)) - .try_into()? - } - Provider::Url(url) => Config::new() - .add_header("Content-Type", "application/json")? - .set_base_url(url.clone()) - .set_timeout(Some(HTTP_REQUEST_TIMEOUT)) - .try_into()?, - Provider::Test => Config::new().try_into()?, - }; - let client = client.with(Retry::default()); + pub async fn new(client: Client, fallback_client: Client) -> Result { // Only check that provider is connected & available if not using a test provider. debug!( - "Starting ExecutionApi with provider at url: {:?}", - client.config().base_url + "Starting ExecutionApi with primary provider: {} and fallback provider: {}", + client + .config() + .base_url + .as_ref() + .expect("to have base url set") + .as_str(), + fallback_client + .config() + .base_url + .as_ref() + .expect("to have base url set") + .as_str() ); - if provider != Provider::Test { - check_provider(&client).await.map_err(|err| { - anyhow!("Check EL provider failed. Provider may be offline: {err:?}") - })?; + if let Err(err) = check_provider(&client).await { + error!("Primary el provider is offline: {err:?}"); } let master_acc = MasterAccumulator::default(); - Ok(Self { client, master_acc }) + Ok(Self { + client, + fallback_client, + master_acc, + }) } /// Return a validated FullHeader & content key / value pair for the given header. @@ -101,7 +78,7 @@ impl ExecutionApi { let block_param = format!("0x{height:01X}"); let params = Params::Array(vec![json!(block_param), json!(true)]); let request = JsonRequest::new("eth_getBlockByNumber".to_string(), params, height as u32); - let response = self.send_request(request).await?; + let response = self.try_request(request).await?; let result = response .get("result") .ok_or_else(|| anyhow!("Unable to fetch header for block: {height:?}"))?; @@ -119,7 +96,6 @@ impl ExecutionApi { if let Err(msg) = full_header.validate() { bail!("Header validation failed: {msg}"); }; - // Construct content key / value pair. let content_key = HistoryContentKey::BlockHeaderWithProof(BlockHeaderKey { block_hash: full_header.header.hash().0, @@ -267,7 +243,7 @@ impl ExecutionApi { let params = Params::Array(vec![json!("latest"), json!(false)]); let method = "eth_getBlockByNumber".to_string(); let request = JsonRequest::new(method, params, 1); - let response = self.send_request(request).await?; + let response = self.try_request(request).await?; let result = response .get("result") .ok_or_else(|| anyhow!("Unable to fetch latest block"))?; @@ -281,7 +257,7 @@ impl ExecutionApi { async fn batch_requests(&self, obj: Vec) -> anyhow::Result { let batched_request_futures = obj .chunks(BATCH_LIMIT) - .map(|chunk| self.send_batch_request(chunk.to_vec())) + .map(|chunk| self.try_batch_request(chunk.to_vec())) .collect::>(); match join_all(batched_request_futures) .await @@ -295,14 +271,31 @@ impl ExecutionApi { } } - async fn send_batch_request(&self, requests: Vec) -> anyhow::Result> { + async fn try_batch_request(&self, requests: Vec) -> anyhow::Result> { if requests.len() > BATCH_LIMIT { warn!( "Attempting to send requests outnumbering provider request limit of {BATCH_LIMIT}." ) } - let result = self - .client + match Self::send_batch_request(&self.client, &requests).await { + Ok(response) => Ok(response), + Err(msg) => { + warn!("Failed to send batch request to primary provider: {msg}"); + match Self::send_batch_request(&self.fallback_client, &requests).await { + Ok(response) => Ok(response), + Err(msg) => { + bail!("Failed to send batch request to fallback provider: {msg}"); + } + } + } + } + } + + async fn send_batch_request( + client: &Client, + requests: &Vec, + ) -> anyhow::Result> { + let result = client .post("") .body_json(&json!(requests)) .map_err(|e| anyhow!("Unable to construct json post for batched requests: {e:?}"))?; @@ -315,9 +308,20 @@ impl ExecutionApi { }) } - async fn send_request(&self, request: JsonRequest) -> anyhow::Result { - let result = self - .client + async fn try_request(&self, request: JsonRequest) -> anyhow::Result { + match Self::send_request(&self.client, &request).await { + Ok(response) => Ok(response), + Err(msg) => { + warn!("Failed to send request to primary provider, retrying with fallback provider: {msg}"); + Self::send_request(&self.fallback_client, &request) + .await + .map_err(|err| anyhow!("Failed to send request to fallback provider: {err:?}")) + } + } + } + + async fn send_request(client: &Client, request: &JsonRequest) -> anyhow::Result { + let result = client .post("") .body_json(&request) .map_err(|e| anyhow!("Unable to construct json post for single request: {e:?}"))?; @@ -343,55 +347,6 @@ pub async fn construct_proof( Ok(HeaderWithProof { header, proof }) } -#[derive(Debug)] -pub struct Retry { - attempts: u8, -} - -impl Retry { - pub fn new(attempts: u8) -> Self { - Retry { attempts } - } -} - -#[async_trait::async_trait] -impl Middleware for Retry { - async fn handle( - &self, - mut req: Request, - client: Client, - next: Next<'_>, - ) -> Result { - let mut retry_count: u8 = 0; - let body = req.take_body().into_bytes().await?; - while retry_count < self.attempts { - if retry_count > 0 { - warn!("Retrying request"); - } - let mut new_req = req.clone(); - new_req.set_body(Body::from_bytes(body.clone())); - if let Ok(val) = next.run(new_req, client.clone()).await { - if val.status().is_success() { - return Ok(val); - } - tracing::error!("Execution client request failed with: {:?}", val); - }; - retry_count += 1; - sleep(Duration::from_millis(100)).await; - } - Err(surf::Error::from_str( - 500, - "Unable to fetch batch after 3 retries", - )) - } -} - -impl Default for Retry { - fn default() -> Self { - Self { attempts: 3 } - } -} - /// Check that provider is valid and accessible. async fn check_provider(client: &Client) -> anyhow::Result<()> { let request = client diff --git a/portal-bridge/src/cli.rs b/portal-bridge/src/cli.rs index d6171de0b..796f6a307 100644 --- a/portal-bridge/src/cli.rs +++ b/portal-bridge/src/cli.rs @@ -1,14 +1,18 @@ -use std::{net::SocketAddr, path::PathBuf, str::FromStr}; +use std::{env, net::SocketAddr, path::PathBuf, str::FromStr}; use alloy_primitives::B256; use clap::{Parser, Subcommand}; +use surf::{Client, Config}; use tokio::process::Child; +use tracing::error; use url::Url; use crate::{ client_handles::{fluffy_handle, trin_handle}, - constants::DEFAULT_GOSSIP_LIMIT, + constants::{DEFAULT_GOSSIP_LIMIT, HTTP_REQUEST_TIMEOUT}, types::{mode::BridgeMode, network::NetworkKind}, + DEFAULT_BASE_CL_ENDPOINT, DEFAULT_BASE_EL_ENDPOINT, FALLBACK_BASE_CL_ENDPOINT, + FALLBACK_BASE_EL_ENDPOINT, }; use ethportal_api::types::cli::{ check_private_key_length, DEFAULT_DISCOVERY_PORT, DEFAULT_WEB3_HTTP_PORT, @@ -21,7 +25,7 @@ use ethportal_api::types::cli::{ pub const MAX_NODE_COUNT: u8 = 16; const DEFAULT_SUBNETWORK: &str = "history"; -#[derive(Parser, Debug, PartialEq, Clone)] +#[derive(Parser, Debug, Clone)] #[command(name = "Trin Bridge", about = "Feed the network")] pub struct BridgeConfig { #[arg( @@ -93,17 +97,35 @@ pub struct BridgeConfig { #[arg( long = "el-provider", - default_value_t = Provider::PandaOps, - help = "Data provider for execution layer data. (\"pandaops\" / infura url with api key / local node url)" + default_value = DEFAULT_BASE_EL_ENDPOINT, + help = "Data provider for execution layer data. (pandaops url / infura url with api key / local node url)", + value_parser = url_to_client )] - pub el_provider: Provider, + pub el_provider: Client, + + #[arg( + long = "el-provider-fallback", + default_value = FALLBACK_BASE_EL_ENDPOINT, + help = "Data provider for execution layer data. (pandaops url / infura url with api key / local node url)", + value_parser = url_to_client + )] + pub el_provider_fallback: Client, #[arg( long = "cl-provider", - default_value_t = Provider::PandaOps, - help = "Data provider for consensus layer data. (\"pandaops\" / local node url)" + default_value = DEFAULT_BASE_CL_ENDPOINT, + help = "Data provider for consensus layer data. (pandaops url / local node url)", + value_parser = url_to_client + )] + pub cl_provider: Client, + + #[arg( + long = "cl-provider-fallback", + default_value = FALLBACK_BASE_CL_ENDPOINT, + help = "Data provider for consensus layer data. (pandaops url / local node url)", + value_parser = url_to_client )] - pub cl_provider: Provider, + pub cl_provider_fallback: Client, #[arg( default_value_t = DEFAULT_DISCOVERY_PORT, @@ -136,42 +158,41 @@ fn check_node_count(val: &str) -> Result { } } -type ParseError = &'static str; - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub enum Provider { - #[default] - PandaOps, - // the url for a local provider or 3rd party provider (like Infura) - Url(Url), - // stub provider for use in test mode - Test, -} - -impl FromStr for Provider { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match s { - "pandaops" => Ok(Provider::PandaOps), - "test" => Ok(Provider::Test), - _ => match Url::parse(s) { - Ok(url) => Ok(Provider::Url(url)), - Err(_) => Err("Invalid provider: must be 'pandaops' or a valid url"), - }, - } +fn url_to_client(url: &str) -> Result { + let mut config = Config::new() + .add_header("Content-Type", "application/json") + .expect("to be able to add content type header") + .set_timeout(Some(HTTP_REQUEST_TIMEOUT)); + let url = Url::parse(url).map_err(|_| "Invalid provider url".to_string())?; + if url + .host_str() + .expect("to find host string") + .contains("pandaops.io") + { + let client_id = env::var("PANDAOPS_CLIENT_ID").unwrap_or_else(|_| { + error!("Pandoaps provider detected without PANDAOPS_CLIENT_ID set"); + "null".to_string() + }); + let client_secret = env::var("PANDAOPS_CLIENT_SECRET").unwrap_or_else(|_| { + error!("Pandoaps provider detected without PANDAOPS_CLIENT_SECRET set"); + "null".to_string() + }); + config = config + .clone() + .add_header("CF-Access-Client-Id", client_id) + .map_err(|_| "to be able to add client id header")? + .add_header("CF-Access-Client-Secret", client_secret) + .map_err(|_| "to be able to add client secret header")? + .set_base_url(url); + } else { + config = config.set_base_url(url); } + Ok(config + .try_into() + .map_err(|_| "to convert config to client")?) } -impl ToString for Provider { - fn to_string(&self) -> String { - match self { - Provider::PandaOps => "pandaops".to_string(), - Provider::Test => "test".to_string(), - Provider::Url(url) => format!("{url}"), - } - } -} +type ParseError = &'static str; #[derive(Clone, Debug, PartialEq, Eq, Subcommand)] pub enum ClientType { @@ -235,8 +256,38 @@ mod test { ); assert_eq!(bridge_config.mode, BridgeMode::Latest); assert_eq!(bridge_config.epoch_acc_path, PathBuf::from(EPOCH_ACC_PATH)); - assert_eq!(bridge_config.el_provider, Provider::PandaOps); - assert_eq!(bridge_config.cl_provider, Provider::PandaOps); + assert!(bridge_config + .el_provider + .config() + .base_url + .as_ref() + .unwrap() + .as_str() + .contains(DEFAULT_BASE_EL_ENDPOINT)); + assert!(bridge_config + .el_provider_fallback + .config() + .base_url + .as_ref() + .unwrap() + .as_str() + .contains(FALLBACK_BASE_EL_ENDPOINT)); + assert!(bridge_config + .cl_provider + .config() + .base_url + .as_ref() + .unwrap() + .as_str() + .contains(DEFAULT_BASE_CL_ENDPOINT)); + assert!(bridge_config + .cl_provider_fallback + .config() + .base_url + .as_ref() + .unwrap() + .as_str() + .contains(FALLBACK_BASE_CL_ENDPOINT)); assert_eq!( bridge_config.network, vec![NetworkKind::History, NetworkKind::Beacon] diff --git a/portal-bridge/src/lib.rs b/portal-bridge/src/lib.rs index 41ecd63b9..351a9224c 100644 --- a/portal-bridge/src/lib.rs +++ b/portal-bridge/src/lib.rs @@ -11,9 +11,6 @@ pub mod stats; pub mod types; pub mod utils; -use lazy_static::lazy_static; -use std::env; - // PANDAOPS refers to the group of clients provisioned by the EF devops team. // These are only intended to be used by core team members who have access to the nodes. // @@ -22,23 +19,9 @@ use std::env; // format), shackle is known to be somewhat buggy has caused some invalid responses. // Reth's archive node, has also exhibited some problems with the concurrent requests rate we // currently use. -const DEFAULT_BASE_EL_ENDPOINT: &str = "https://geth-lighthouse.mainnet.eu1.ethpandaops.io/"; -// The `erigon` endpoint appears to perform much better for backfill requests. -const DEFAULT_BASE_EL_ARCHIVE_ENDPOINT: &str = - "https://erigon-lighthouse.mainnet.eu1.ethpandaops.io/"; +pub const DEFAULT_BASE_EL_ENDPOINT: &str = "https://geth-lighthouse.mainnet.eu1.ethpandaops.io/"; +pub const FALLBACK_BASE_EL_ENDPOINT: &str = "https://geth-lighthouse.mainnet.eu1.ethpandaops.io/"; /// Consensus layer PandaOps endpoint /// We use Nimbus as the CL client, because it supports light client data by default. -const DEFAULT_BASE_CL_ENDPOINT: &str = "https://nimbus.mainnet.eu1.ethpandaops.io/"; - -lazy_static! { - pub static ref PANDAOPS_CLIENT_ID: String = - env::var("PANDAOPS_CLIENT_ID").expect("PANDAOPS_CLIENT_ID env var not set."); - pub static ref PANDAOPS_CLIENT_SECRET: String = - env::var("PANDAOPS_CLIENT_SECRET").expect("PANDAOPS_CLIENT_SECRET env var not set."); - static ref BASE_EL_ENDPOINT: String = - env::var("BASE_EL_ENDPOINT").unwrap_or_else(|_| DEFAULT_BASE_EL_ENDPOINT.to_string()); - static ref BASE_EL_ARCHIVE_ENDPOINT: String = env::var("BASE_EL_ARCHIVE_ENDPOINT") - .unwrap_or_else(|_| DEFAULT_BASE_EL_ARCHIVE_ENDPOINT.to_string()); - static ref BASE_CL_ENDPOINT: String = - env::var("BASE_CL_ENDPOINT").unwrap_or_else(|_| DEFAULT_BASE_CL_ENDPOINT.to_string()); -} +pub const DEFAULT_BASE_CL_ENDPOINT: &str = "https://nimbus.mainnet.eu1.ethpandaops.io/"; +pub const FALLBACK_BASE_CL_ENDPOINT: &str = "https://nimbus.mainnet.eu1.ethpandaops.io/"; diff --git a/portal-bridge/src/main.rs b/portal-bridge/src/main.rs index 78c5ac075..06511d6a8 100644 --- a/portal-bridge/src/main.rs +++ b/portal-bridge/src/main.rs @@ -64,7 +64,11 @@ async fn main() -> Result<(), Box> { let portal_clients = portal_clients .clone() .expect("Failed to create beacon JSON-RPC clients"); - let consensus_api = ConsensusApi::new(bridge_config.cl_provider).await?; + let consensus_api = ConsensusApi::new( + bridge_config.cl_provider, + bridge_config.cl_provider_fallback, + ) + .await?; let bridge_handle = tokio::spawn(async move { let beacon_bridge = BeaconBridge::new(consensus_api, bridge_mode, Arc::new(portal_clients)); @@ -101,9 +105,11 @@ async fn main() -> Result<(), Box> { bridge_tasks.push(bridge_handle); } _ => { - let execution_api = - ExecutionApi::new(bridge_config.el_provider, bridge_config.mode.clone()) - .await?; + let execution_api = ExecutionApi::new( + bridge_config.el_provider, + bridge_config.el_provider_fallback, + ) + .await?; let bridge_handle = tokio::spawn(async move { let master_acc = MasterAccumulator::default(); let header_oracle = HeaderOracle::new(master_acc); diff --git a/src/bin/test_providers.rs b/src/bin/test_providers.rs index 806db27c6..52154acfc 100644 --- a/src/bin/test_providers.rs +++ b/src/bin/test_providers.rs @@ -6,16 +6,21 @@ use rand::{ distributions::{Distribution, Uniform}, thread_rng, }; +use serde_json::json; use ssz::Decode; use surf::{Client, Config}; use tracing::{debug, info, warn}; use url::Url; -use ethportal_api::{types::execution::accumulator::EpochAccumulator, utils::bytes::hex_encode}; -use portal_bridge::{ - api::execution::ExecutionApi, bridge::history::EPOCH_SIZE, cli::Provider, - types::mode::BridgeMode, PANDAOPS_CLIENT_ID, PANDAOPS_CLIENT_SECRET, +use ethportal_api::{ + types::{ + execution::accumulator::EpochAccumulator, + jsonrpc::{params::Params, request::JsonRequest}, + }, + utils::bytes::hex_encode, + Header, }; +use portal_bridge::{api::execution::ExecutionApi, bridge::history::EPOCH_SIZE}; use trin_utils::log::init_tracing_logger; use trin_validation::{ accumulator::MasterAccumulator, @@ -26,6 +31,11 @@ use trin_validation::{ }, }; +lazy_static::lazy_static! { + static ref PANDAOPS_CLIENT_ID: String = std::env::var("PANDAOPS_CLIENT_ID").unwrap(); + static ref PANDAOPS_CLIENT_SECRET: String = std::env::var("PANDAOPS_CLIENT_SECRET").unwrap(); +} + // tldr: // Randomly samples X blocks from every hard fork range. // Validates that each provider is able to return valid @@ -43,10 +53,8 @@ use trin_validation::{ pub async fn main() -> Result<()> { init_tracing_logger(); let config = ProviderConfig::parse(); - let api = ExecutionApi::new(Provider::PandaOps, BridgeMode::Latest) - .await - .unwrap(); - let latest_block = api.get_latest_block_number().await?; + let latest_block = get_latest_block_number().await?; + info!("Starting to test providers: latest block = {latest_block}"); let mut all_ranges = Ranges::into_vec(config.sample_size, latest_block); let mut all_providers: Vec = Providers::into_vec(); for provider in all_providers.iter_mut() { @@ -54,6 +62,7 @@ pub async fn main() -> Result<()> { let mut provider_failures = 0; let client = provider.get_client(); let api = ExecutionApi { + fallback_client: client.clone(), client, master_acc: MasterAccumulator::default(), }; @@ -437,3 +446,29 @@ impl Providers { } } } + +async fn get_latest_block_number() -> Result { + let config = Config::new() + .add_header("Content-Type", "application/json") + .unwrap() + .add_header("CF-Access-Client-Id", PANDAOPS_CLIENT_ID.to_string()) + .unwrap() + .add_header( + "CF-Access-Client-Secret", + PANDAOPS_CLIENT_SECRET.to_string(), + ) + .unwrap() + .set_base_url(Url::parse("https://geth-lighthouse.mainnet.eu1.ethpandaops.io/").unwrap()); + let client: Client = config.try_into()?; + let params = Params::Array(vec![json!("latest"), json!(false)]); + let method = "eth_getBlockByNumber".to_string(); + let request = JsonRequest::new(method, params, 1); + let response = client.post("").body_json(&request).unwrap(); + let response = response.recv_string().await.unwrap(); + let response = serde_json::from_str::(&response)?; + let result = response + .get("result") + .ok_or_else(|| anyhow!("Unable to fetch latest block"))?; + let header: Header = serde_json::from_value(result.clone())?; + Ok(header.number) +}