Skip to content
Open
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
12 changes: 8 additions & 4 deletions crates/apollo_base_layer_tests/src/anvil_base_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use alloy::sol_types::SolValue;
use async_trait::async_trait;
use colored::*;
use papyrus_base_layer::ethereum_base_layer_contract::{
CircularUrlIterator,
EthereumBaseLayerConfig,
EthereumBaseLayerContract,
EthereumBaseLayerError,
Expand Down Expand Up @@ -96,14 +97,13 @@ curl -L \
Starknet::deploy(anvil_client.clone()).await.unwrap();

let config = Self::config();
let url =
config.ordered_l1_endpoint_urls.first().expect("No endpoint URLs provided").clone();
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
let root_client = anvil_client.root().clone();
let contract = Starknet::new(config.starknet_contract_address, root_client);

let anvil_base_layer = Self {
anvil_provider: anvil_client.erased(),
ethereum_base_layer: EthereumBaseLayerContract { config, contract, url },
ethereum_base_layer: EthereumBaseLayerContract { config, contract, url_iterator },
};
anvil_base_layer.initialize_mocked_starknet_contract().await;

Expand Down Expand Up @@ -229,12 +229,16 @@ impl BaseLayerContract for AnvilBaseLayer {

// TODO(Arni): Consider deleting this function from the trait.
async fn get_url(&self) -> Result<Url, Self::Error> {
Ok(self.ethereum_base_layer.url.clone())
Ok(self.ethereum_base_layer.url_iterator.get_current_url())
}

async fn set_provider_url(&mut self, _url: Url) -> Result<(), Self::Error> {
unimplemented!("Anvil base layer is tied to a an Anvil server, url is fixed.")
}

async fn cycle_provider_url(&mut self) -> Result<(), Self::Error> {
unimplemented!("Anvil base layer is tied to a an Anvil server, url is fixed.")
}
}

/// Converts a given [L1 handler transaction](starknet_api::transaction::L1HandlerTransaction)
Expand Down
5 changes: 3 additions & 2 deletions crates/papyrus_base_layer/src/base_layer_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use alloy::rpc::types::{Block, BlockTransactions, Header as AlloyRpcHeader};
use pretty_assertions::assert_eq;

use crate::ethereum_base_layer_contract::{
CircularUrlIterator,
EthereumBaseLayerConfig,
EthereumBaseLayerContract,
Starknet,
Expand All @@ -25,8 +26,8 @@ fn base_layer_with_mocked_provider() -> (EthereumBaseLayerContract, Asserter) {
let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone()).root().clone();
let contract = Starknet::new(Default::default(), provider);
let config = EthereumBaseLayerConfig::default();
let url = config.ordered_l1_endpoint_urls.first().expect("No endpoint URLs provided").clone();
let base_layer = EthereumBaseLayerContract { contract, config, url };
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
let base_layer = EthereumBaseLayerContract { contract, config, url_iterator };

(base_layer, asserter)
}
Expand Down
54 changes: 48 additions & 6 deletions crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,46 @@ sol!(
/// L2 from this contract, which appear on the corresponding base layer.
pub type StarknetL1Contract = Starknet::StarknetInstance<RootProvider, Ethereum>;

#[derive(Clone, Debug)]
pub struct CircularUrlIterator {
urls: Vec<Url>,
index: usize,
}

impl CircularUrlIterator {
pub fn new(urls: Vec<Url>) -> Self {
Self { urls, index: 0 }
}

pub fn get_current_url(&self) -> Url {
self.urls.get(self.index).cloned().expect("No endpoint URLs provided")
}
}

impl Iterator for CircularUrlIterator {
type Item = Url;

fn next(&mut self) -> Option<Self::Item> {
self.index = (self.index + 1) % self.urls.len();
self.urls.get(self.index).cloned()
}
}

#[derive(Clone, Debug)]
pub struct EthereumBaseLayerContract {
pub url: Url,
pub url_iterator: CircularUrlIterator,
pub config: EthereumBaseLayerConfig,
pub contract: StarknetL1Contract,
}

impl EthereumBaseLayerContract {
pub fn new(config: EthereumBaseLayerConfig) -> Self {
let url =
config.ordered_l1_endpoint_urls.first().expect("No endpoint URLs provided").clone();
let contract = build_contract_instance(config.starknet_contract_address, url.clone());
Self { url, contract, config }
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
let contract = build_contract_instance(
config.starknet_contract_address,
url_iterator.get_current_url(),
);
Self { url_iterator, contract, config }
}
}

Expand Down Expand Up @@ -221,14 +248,21 @@ impl BaseLayerContract for EthereumBaseLayerContract {
}

async fn get_url(&self) -> Result<Url, Self::Error> {
Ok(self.url.clone())
Ok(self.url_iterator.get_current_url())
}

/// Rebuilds the provider on the new url.
async fn set_provider_url(&mut self, url: Url) -> Result<(), Self::Error> {
self.contract = build_contract_instance(self.config.starknet_contract_address, url.clone());
Ok(())
}

async fn cycle_provider_url(&mut self) -> Result<(), Self::Error> {
self.url_iterator
.next()
.expect("URL list was validated to be non-empty when config was loaded");
Ok(())
}
}

#[derive(thiserror::Error, Debug)]
Expand Down Expand Up @@ -285,6 +319,7 @@ impl Validate for EthereumBaseLayerConfig {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
let mut errors = validator::ValidationErrors::new();

// Check that the Fusaka updates are ordered chronologically.
if self.fusaka_no_bpo_start_block_number > self.bpo1_start_block_number {
let mut error = ValidationError::new("block_numbers_not_ordered");
error.message =
Expand All @@ -299,6 +334,13 @@ impl Validate for EthereumBaseLayerConfig {
errors.add("bpo1_start_block_number", error);
}

// Check that the URL list is not empty.
if self.ordered_l1_endpoint_urls.is_empty() {
let mut error = ValidationError::new("url_list_is_empty");
error.message = Some("ordered_l1_endpoint_urls must not be empty".into());
errors.add("ordered_l1_endpoint_urls", error);
}

if errors.is_empty() { Ok(()) } else { Err(errors) }
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/papyrus_base_layer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub trait BaseLayerContract {

async fn get_url(&self) -> Result<Url, Self::Error>;
async fn set_provider_url(&mut self, url: Url) -> Result<(), Self::Error>;
async fn cycle_provider_url(&mut self) -> Result<(), Self::Error>;
}

/// Reference to an L1 block, extend as needed.
Expand Down
8 changes: 8 additions & 0 deletions crates/papyrus_base_layer/src/monitored_base_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for MonitoredBaseLaye
.await
.map_err(|err| MonitoredBaseLayerError::BaseLayerContractError(err))
}

async fn cycle_provider_url(&mut self) -> Result<(), Self::Error> {
self.get()
.await?
.cycle_provider_url()
.await
.map_err(|err| MonitoredBaseLayerError::BaseLayerContractError(err))
}
}

impl<B: BaseLayerContract + Send + Sync + std::fmt::Debug> std::fmt::Debug
Expand Down
Loading