Skip to content

Commit 2495ef9

Browse files
committed
papyrus_base_layer: add the option to cycle the URL directly into the base layer
1 parent 056f7d2 commit 2495ef9

File tree

5 files changed

+68
-12
lines changed

5 files changed

+68
-12
lines changed

crates/apollo_base_layer_tests/src/anvil_base_layer.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use alloy::sol_types::SolValue;
1010
use async_trait::async_trait;
1111
use colored::*;
1212
use papyrus_base_layer::ethereum_base_layer_contract::{
13+
CircularUrlIterator,
1314
EthereumBaseLayerConfig,
1415
EthereumBaseLayerContract,
1516
EthereumBaseLayerError,
@@ -96,14 +97,13 @@ curl -L \
9697
Starknet::deploy(anvil_client.clone()).await.unwrap();
9798

9899
let config = Self::config();
99-
let url =
100-
config.ordered_l1_endpoint_urls.first().expect("No endpoint URLs provided").clone();
100+
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
101101
let root_client = anvil_client.root().clone();
102102
let contract = Starknet::new(config.starknet_contract_address, root_client);
103103

104104
let anvil_base_layer = Self {
105105
anvil_provider: anvil_client.erased(),
106-
ethereum_base_layer: EthereumBaseLayerContract { config, contract, url },
106+
ethereum_base_layer: EthereumBaseLayerContract { config, contract, url_iterator },
107107
};
108108
anvil_base_layer.initialize_mocked_starknet_contract().await;
109109

@@ -229,12 +229,16 @@ impl BaseLayerContract for AnvilBaseLayer {
229229

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

235235
async fn set_provider_url(&mut self, _url: Url) -> Result<(), Self::Error> {
236236
unimplemented!("Anvil base layer is tied to a an Anvil server, url is fixed.")
237237
}
238+
239+
async fn cycle_provider_url(&mut self) -> Result<(), Self::Error> {
240+
unimplemented!("Anvil base layer is tied to a an Anvil server, url is fixed.")
241+
}
238242
}
239243

240244
/// Converts a given [L1 handler transaction](starknet_api::transaction::L1HandlerTransaction)

crates/papyrus_base_layer/src/base_layer_test.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use alloy::rpc::types::{Block, BlockTransactions, Header as AlloyRpcHeader};
66
use pretty_assertions::assert_eq;
77

88
use crate::ethereum_base_layer_contract::{
9+
CircularUrlIterator,
910
EthereumBaseLayerConfig,
1011
EthereumBaseLayerContract,
1112
Starknet,
@@ -25,8 +26,8 @@ fn base_layer_with_mocked_provider() -> (EthereumBaseLayerContract, Asserter) {
2526
let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone()).root().clone();
2627
let contract = Starknet::new(Default::default(), provider);
2728
let config = EthereumBaseLayerConfig::default();
28-
let url = config.ordered_l1_endpoint_urls.first().expect("No endpoint URLs provided").clone();
29-
let base_layer = EthereumBaseLayerContract { contract, config, url };
29+
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
30+
let base_layer = EthereumBaseLayerContract { contract, config, url_iterator };
3031

3132
(base_layer, asserter)
3233
}

crates/papyrus_base_layer/src/ethereum_base_layer_contract.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,46 @@ sol!(
6969
/// L2 from this contract, which appear on the corresponding base layer.
7070
pub type StarknetL1Contract = Starknet::StarknetInstance<RootProvider, Ethereum>;
7171

72+
#[derive(Clone, Debug)]
73+
pub struct CircularUrlIterator {
74+
urls: Vec<Url>,
75+
index: usize,
76+
}
77+
78+
impl CircularUrlIterator {
79+
pub fn new(urls: Vec<Url>) -> Self {
80+
Self { urls, index: 0 }
81+
}
82+
83+
pub fn get_current_url(&self) -> Url {
84+
self.urls.get(self.index).cloned().expect("No endpoint URLs provided")
85+
}
86+
}
87+
88+
impl Iterator for CircularUrlIterator {
89+
type Item = Url;
90+
91+
fn next(&mut self) -> Option<Self::Item> {
92+
self.index = (self.index + 1) % self.urls.len();
93+
self.urls.get(self.index).cloned()
94+
}
95+
}
96+
7297
#[derive(Clone, Debug)]
7398
pub struct EthereumBaseLayerContract {
74-
pub url: Url,
99+
pub url_iterator: CircularUrlIterator,
75100
pub config: EthereumBaseLayerConfig,
76101
pub contract: StarknetL1Contract,
77102
}
78103

79104
impl EthereumBaseLayerContract {
80105
pub fn new(config: EthereumBaseLayerConfig) -> Self {
81-
let url =
82-
config.ordered_l1_endpoint_urls.first().expect("No endpoint URLs provided").clone();
83-
let contract = build_contract_instance(config.starknet_contract_address, url.clone());
84-
Self { url, contract, config }
106+
let url_iterator = CircularUrlIterator::new(config.ordered_l1_endpoint_urls.clone());
107+
let contract = build_contract_instance(
108+
config.starknet_contract_address,
109+
url_iterator.get_current_url(),
110+
);
111+
Self { url_iterator, contract, config }
85112
}
86113
}
87114

@@ -221,14 +248,21 @@ impl BaseLayerContract for EthereumBaseLayerContract {
221248
}
222249

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

227254
/// Rebuilds the provider on the new url.
228255
async fn set_provider_url(&mut self, url: Url) -> Result<(), Self::Error> {
229256
self.contract = build_contract_instance(self.config.starknet_contract_address, url.clone());
230257
Ok(())
231258
}
259+
260+
async fn cycle_provider_url(&mut self) -> Result<(), Self::Error> {
261+
self.url_iterator
262+
.next()
263+
.expect("URL list was validated to be non-empty when config was loaded");
264+
Ok(())
265+
}
232266
}
233267

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

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

337+
// Check that the URL list is not empty.
338+
if self.ordered_l1_endpoint_urls.is_empty() {
339+
let mut error = ValidationError::new("url_list_is_empty");
340+
error.message = Some("ordered_l1_endpoint_urls must not be empty".into());
341+
errors.add("ordered_l1_endpoint_urls", error);
342+
}
343+
302344
if errors.is_empty() { Ok(()) } else { Err(errors) }
303345
}
304346
}

crates/papyrus_base_layer/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub trait BaseLayerContract {
8080

8181
async fn get_url(&self) -> Result<Url, Self::Error>;
8282
async fn set_provider_url(&mut self, url: Url) -> Result<(), Self::Error>;
83+
async fn cycle_provider_url(&mut self) -> Result<(), Self::Error>;
8384
}
8485

8586
/// Reference to an L1 block, extend as needed.

crates/papyrus_base_layer/src/monitored_base_layer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ impl<B: BaseLayerContract + Send + Sync> BaseLayerContract for MonitoredBaseLaye
153153
.await
154154
.map_err(|err| MonitoredBaseLayerError::BaseLayerContractError(err))
155155
}
156+
157+
async fn cycle_provider_url(&mut self) -> Result<(), Self::Error> {
158+
self.get()
159+
.await?
160+
.cycle_provider_url()
161+
.await
162+
.map_err(|err| MonitoredBaseLayerError::BaseLayerContractError(err))
163+
}
156164
}
157165

158166
impl<B: BaseLayerContract + Send + Sync + std::fmt::Debug> std::fmt::Debug

0 commit comments

Comments
 (0)