Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions crates/papyrus_base_layer/src/base_layer_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::types::{Block, BlockTransactions, Header as AlloyRpcHeader};
use pretty_assertions::assert_eq;

use crate::ethereum_base_layer_contract::{
CircularUrlIterator,
EthereumBaseLayerConfig,
EthereumBaseLayerContract,
Starknet,
};
use crate::ethereum_base_layer_contract::{EthereumBaseLayerConfig, EthereumBaseLayerContract};
use crate::BaseLayerContract;

// TODO(Gilad): Use everywhere instead of relying on the confusing `#[ignore]` api to mark slow
Expand All @@ -24,10 +19,8 @@ fn base_layer_with_mocked_provider() -> (EthereumBaseLayerContract, Asserter) {
let asserter = Asserter::new();

let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone()).root().clone();
let contract = Starknet::new(Default::default(), provider);
let config = EthereumBaseLayerConfig::default();
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
let base_layer = EthereumBaseLayerContract { contract, config, url_iterator };
let base_layer = EthereumBaseLayerContract::new_with_provider(config, provider);

(base_layer, asserter)
}
Expand Down
92 changes: 62 additions & 30 deletions crates/papyrus_base_layer/src/cyclic_base_layer_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::ops::RangeInclusive;

use async_trait::async_trait;
use starknet_api::block::BlockHashAndNumber;
use tracing::info;
use url::Url;

use crate::{BaseLayerContract, L1BlockHeader, L1BlockNumber, L1BlockReference, L1Event};
Expand All @@ -18,6 +19,52 @@ impl<B: BaseLayerContract + Send + Sync> CyclicBaseLayerWrapper<B> {
pub fn new(base_layer: B) -> Self {
Self { base_layer }
}

// Check the result of a function call to the base layer. If it fails, cycle the URL and signal
// the caller that we should try again (by returning None).
async fn cycle_url_on_error<ReturnType: std::fmt::Debug>(
&mut self,
start_url: &Url,
result: Result<ReturnType, B::Error>,
) -> Option<Result<ReturnType, B::Error>> {
// In case we succeed, just return the (successful) result.
if result.is_ok() {
return Some(result);
}
// Get the current URL (return error in case it fails to get it).
let current_url_result = self.base_layer.get_url().await;
let Ok(current_url) = current_url_result else {
return Some(Err(current_url_result.expect_err("result is checked at let-else")));
};
// Otherwise, cycle the URL so we can try again. Return error in case it fails to cycle.
let cycle_url_result = self.base_layer.cycle_provider_url().await;
let Ok(()) = cycle_url_result else {
return Some(Err(cycle_url_result.expect_err("result is checked at let-else")));
};
// Get the new URL (return error in case it fails to get it).
let new_url_result = self.base_layer.get_url().await;
let Ok(new_url) = new_url_result else {
return Some(Err(new_url_result.expect_err("result is checked at let-else")));
};
info!(
"Cycling URL from {:?} to {:?}",
to_safe_string(&current_url),
to_safe_string(&new_url)
);

// If we've cycled back to the start URL, we need to return the last error we got.
if &new_url == start_url {
info!(
"Cycled back to start URL {:?}, returning error {:?}.",
to_safe_string(start_url),
result
);
return Some(result);
}
// If we cycled but still haven't reached the start URL, we return None to signal that we
// should try again with the new URL.
None
}
}

#[async_trait]
Expand All @@ -31,12 +78,8 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for CyclicBaseLayerWr
let start_url = self.base_layer.get_url().await?;
loop {
let result = self.base_layer.get_proved_block_at(l1_block).await;
if result.is_ok() {
return result;
}
self.base_layer.cycle_provider_url().await?;
if self.base_layer.get_url().await? == start_url {
return result;
if let Some(result) = self.cycle_url_on_error(&start_url, result).await {
return result; // Could return a success or an error.
}
}
}
Expand All @@ -45,12 +88,8 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for CyclicBaseLayerWr
let start_url = self.base_layer.get_url().await?;
loop {
let result = self.base_layer.latest_l1_block_number().await;
if result.is_ok() {
return result;
}
self.base_layer.cycle_provider_url().await?;
if self.base_layer.get_url().await? == start_url {
return result;
if let Some(result) = self.cycle_url_on_error(&start_url, result).await {
return result; // Could return a success or an error.
}
}
}
Expand All @@ -62,12 +101,8 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for CyclicBaseLayerWr
let start_url = self.base_layer.get_url().await?;
loop {
let result = self.base_layer.l1_block_at(block_number).await;
if result.is_ok() {
return result;
}
self.base_layer.cycle_provider_url().await?;
if self.base_layer.get_url().await? == start_url {
return result;
if let Some(result) = self.cycle_url_on_error(&start_url, result).await {
return result; // Could return a success or an error.
}
}
}
Expand All @@ -80,12 +115,8 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for CyclicBaseLayerWr
let start_url = self.base_layer.get_url().await?;
loop {
let result = self.base_layer.events(block_range.clone(), event_identifiers).await;
if result.is_ok() {
return result;
}
self.base_layer.cycle_provider_url().await?;
if self.base_layer.get_url().await? == start_url {
return result;
if let Some(result) = self.cycle_url_on_error(&start_url, result).await {
return result; // Could return a success or an error.
}
}
}
Expand All @@ -97,12 +128,8 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for CyclicBaseLayerWr
let start_url = self.base_layer.get_url().await?;
loop {
let result = self.base_layer.get_block_header(block_number).await;
if result.is_ok() {
return result;
}
self.base_layer.cycle_provider_url().await?;
if self.base_layer.get_url().await? == start_url {
return result;
if let Some(result) = self.cycle_url_on_error(&start_url, result).await {
return result; // Could return a success or an error.
}
}
}
Expand All @@ -126,3 +153,8 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for CyclicBaseLayerWr
self.base_layer.cycle_provider_url().await
}
}

fn to_safe_string(url: &Url) -> String {
// We print only the hostnames to avoid leaking the API keys.
url.host().map_or_else(|| "no host in url!".to_string(), |host| host.to_string())
}
12 changes: 6 additions & 6 deletions crates/papyrus_base_layer/src/cyclic_base_layer_wrapper_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const NUM_URLS: usize = 2;
async fn cycle_get_proved_block_at(#[case] num_failing_calls: usize) {
// Setup.
let success = num_failing_calls < NUM_URLS;
let expected_num_url_calls = num_failing_calls + 1;
let expected_num_url_calls = num_failing_calls * 2 + 1;
let num_url_calls_made = Arc::new(AtomicUsize::new(0));
let num_url_calls_made_clone = num_url_calls_made.clone();
let num_url_calls_made_clone2 = num_url_calls_made.clone();
Expand Down Expand Up @@ -73,7 +73,7 @@ async fn cycle_get_proved_block_at(#[case] num_failing_calls: usize) {
async fn cycle_latest_l1_block_number(#[case] num_failing_calls: usize) {
// Setup.
let success = num_failing_calls < NUM_URLS;
let expected_num_url_calls = num_failing_calls + 1;
let expected_num_url_calls = num_failing_calls * 2 + 1;
let num_url_calls_made = Arc::new(AtomicUsize::new(0));
let num_url_calls_made_clone = num_url_calls_made.clone();
let num_url_calls_made_clone2 = num_url_calls_made.clone();
Expand Down Expand Up @@ -113,7 +113,7 @@ async fn cycle_latest_l1_block_number(#[case] num_failing_calls: usize) {
async fn cycle_l1_block_at(#[case] num_failing_calls: usize) {
// Setup.
let success = num_failing_calls < NUM_URLS;
let expected_num_url_calls = num_failing_calls + 1;
let expected_num_url_calls = num_failing_calls * 2 + 1;
let num_url_calls_made = Arc::new(AtomicUsize::new(0));
let num_url_calls_made_clone = num_url_calls_made.clone();
let num_url_calls_made_clone2 = num_url_calls_made.clone();
Expand Down Expand Up @@ -156,7 +156,7 @@ async fn cycle_l1_block_at(#[case] num_failing_calls: usize) {
async fn cycle_events(#[case] num_failing_calls: usize) {
// Setup.
let success = num_failing_calls < NUM_URLS;
let expected_num_url_calls = num_failing_calls + 1;
let expected_num_url_calls = num_failing_calls * 2 + 1;
let num_url_calls_made = Arc::new(AtomicUsize::new(0));
let num_url_calls_made_clone = num_url_calls_made.clone();
let num_url_calls_made_clone2 = num_url_calls_made.clone();
Expand Down Expand Up @@ -196,7 +196,7 @@ async fn cycle_events(#[case] num_failing_calls: usize) {
async fn cycle_get_block_header(#[case] num_failing_calls: usize) {
// Setup.
let success = num_failing_calls < NUM_URLS;
let expected_num_url_calls = num_failing_calls + 1;
let expected_num_url_calls = num_failing_calls * 2 + 1;
let num_url_calls_made = Arc::new(AtomicUsize::new(0));
let num_url_calls_made_clone = num_url_calls_made.clone();
let num_url_calls_made_clone2 = num_url_calls_made.clone();
Expand Down Expand Up @@ -255,7 +255,7 @@ async fn get_block_header_fails_after_cycle_error() {
let mut base_layer = MockBaseLayerContract::new();
base_layer
.expect_get_url()
.times(1)
.times(2)
.returning(move || Ok(Url::parse("http://first_endpoint").unwrap()));
base_layer.expect_get_block_header().times(1).returning(move |_| Err(MockError::MockError));
base_layer.expect_cycle_provider_url().times(1).returning(move || Err(MockError::MockError));
Expand Down
7 changes: 7 additions & 0 deletions crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ impl EthereumBaseLayerContract {
);
Self { url_iterator, contract, config }
}
#[cfg(any(test, feature = "testing"))]
pub fn new_with_provider(config: EthereumBaseLayerConfig, provider: RootProvider) -> Self {
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
let starknet_contract_address = config.starknet_contract_address;
let contract = Starknet::new(starknet_contract_address, provider);
Self { url_iterator, contract, config }
}
}

#[async_trait]
Expand Down
Loading