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
53 changes: 33 additions & 20 deletions crates/apollo_l1_gas_price/src/l1_gas_price_scraper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use async_trait::async_trait;
use papyrus_base_layer::{BaseLayerContract, L1BlockHeader, L1BlockNumber};
use starknet_api::block::GasPrice;
use thiserror::Error;
use tracing::{error, info, trace};
use tracing::{error, info, trace, warn};

use crate::metrics::{
register_scraper_metrics,
Expand Down Expand Up @@ -63,6 +63,31 @@ impl<B: BaseLayerContract + Send + Sync + Debug> L1GasPriceScraper<B> {
Self { config, l1_gas_price_provider, base_layer, last_l1_header: None }
}

pub async fn get_first_block_number(&mut self) -> L1BlockNumber {
if let Some(block) = self.config.starting_block {
return block;
};
loop {
let latest = self.latest_l1_block_number().await;
let Ok(latest) = latest else {
warn!(
"Scraper startup failure: could not get the latest L1 block number: {latest:?}"
);
L1_GAS_PRICE_SCRAPER_BASELAYER_ERROR_COUNT.increment(1);
tokio::time::sleep(self.config.polling_interval).await;
continue;
};
// If no starting block is provided, the default is to start from
// startup_num_blocks_multiplier * number_of_blocks_for_mean before the tip of
// L1. Note that for new chains this subtraction may be
// negative, hence the use of saturating_sub.
let latest = latest.saturating_sub(
self.config.number_of_blocks_for_mean * self.config.startup_num_blocks_multiplier,
);
return latest;
}
}

/// Run the scraper, starting from the given L1 `block_number`, indefinitely.
pub async fn run(&mut self, mut block_number: L1BlockNumber) -> L1GasPriceScraperResult<(), B> {
self.l1_gas_price_provider
Expand Down Expand Up @@ -174,24 +199,12 @@ where
async fn start(&mut self) {
info!("Starting component {}.", type_name::<Self>());
register_scraper_metrics();
let start_from = match self.config.starting_block {
Some(block) => block,
None => {
let latest = self
.latest_l1_block_number()
.await
.expect("Failed to get the latest L1 block number at startup");

// If no starting block is provided, the default is to start from
// startup_num_blocks_multiplier * number_of_blocks_for_mean before the tip of L1.
// Note that for new chains this subtraction may be negative,
// hence the use of saturating_sub.
latest.saturating_sub(
self.config.number_of_blocks_for_mean
* self.config.startup_num_blocks_multiplier,
)
}
};
self.run(start_from).await.unwrap_or_else(|e| panic!("L1 gas price scraper failed: {e}"))
// Loop and retry base layer until we successfully get the first block number.
let first_block_number = self.get_first_block_number().await;

// Run the scraper on an endless loop.
self.run(first_block_number)
.await
.unwrap_or_else(|e| panic!("L1 gas price scraper failed: {e}"))
}
}
38 changes: 37 additions & 1 deletion crates/apollo_l1_gas_price/src/l1_gas_price_scraper_test.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;

use apollo_l1_gas_price_types::{GasPriceData, MockL1GasPriceProviderClient};
use assert_matches::assert_matches;
use papyrus_base_layer::{L1BlockHash, L1BlockHeader, MockBaseLayerContract};
use papyrus_base_layer::{L1BlockHash, L1BlockHeader, MockBaseLayerContract, MockError};
use rstest::rstest;
use starknet_api::block::GasPrice;

Expand Down Expand Up @@ -323,4 +324,39 @@ async fn base_layer_returns_block_number_below_finality_causes_error() {
);
}

#[tokio::test]
async fn get_first_block_fails_then_succeeds() {
const NUM_BLOCKS_FOR_MEAN: u64 = 2;
const STARTUP_NUM_BLOCKS_MULTIPLIER: u64 = 3;
const LATEST_BLOCK_NUMBER: u64 = 10;
let expected_start_block_number =
LATEST_BLOCK_NUMBER - NUM_BLOCKS_FOR_MEAN * STARTUP_NUM_BLOCKS_MULTIPLIER;

let mut mock_contract = MockBaseLayerContract::new();
// First fail.
mock_contract
.expect_latest_l1_block_number()
.times(1)
.returning(move || Err(MockError::MockError));

// Then succeed.
mock_contract
.expect_latest_l1_block_number()
.times(1)
.returning(move || Ok(LATEST_BLOCK_NUMBER));

let mut scraper = L1GasPriceScraper::new(
L1GasPriceScraperConfig {
polling_interval: Duration::from_secs(0),
number_of_blocks_for_mean: NUM_BLOCKS_FOR_MEAN,
startup_num_blocks_multiplier: STARTUP_NUM_BLOCKS_MULTIPLIER,
..Default::default()
},
Arc::new(MockL1GasPriceProviderClient::new()),
mock_contract,
);
let first_block_number = scraper.get_first_block_number().await;
assert_eq!(first_block_number, expected_start_block_number);
}

// TODO(guyn): test scraper with a provider timeout