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
1 change: 0 additions & 1 deletion crates/apollo_consensus_manager/src/consensus_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,6 @@ impl ConsensusManager {
outbound_proposal_sender: outbound_internal_sender,
vote_broadcast_client: votes_broadcast_channels.broadcast_topic_client.clone(),
config_manager_client: Some(Arc::clone(&config_manager_client)),
strk_to_usd_oracle: None,
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use apollo_config::behavior_mode::BehaviorMode;
use apollo_config_manager_types::communication::SharedConfigManagerClient;
use apollo_consensus::types::{ConsensusContext, ConsensusError, ProposalCommitment, Round};
use apollo_consensus_orchestrator_config::config::ContextConfig;
use apollo_l1_gas_price_types::{ExchangeRateOracleClientTrait, L1GasPriceProviderClient};
use apollo_l1_gas_price_types::L1GasPriceProviderClient;
use apollo_network::network_manager::{BroadcastTopicClient, BroadcastTopicClientTrait};
use apollo_protobuf::consensus::{
BuildParam,
Expand Down Expand Up @@ -75,7 +75,7 @@ use tokio::task::JoinHandle;
use tokio::time::sleep;
use tokio_util::sync::CancellationToken;
use tokio_util::task::AbortOnDropHandle;
use tracing::{debug, error, error_span, info, instrument, trace, warn, Instrument};
use tracing::{error, error_span, info, instrument, trace, warn, Instrument};

use crate::build_proposal::{build_proposal, BuildProposalError, ProposalBuildArguments};
use crate::cende::{
Expand Down Expand Up @@ -269,10 +269,6 @@ pub struct SequencerConsensusContextDeps {
// Used to broadcast votes to other consensus nodes.
pub vote_broadcast_client: BroadcastTopicClient<Vote>,
pub config_manager_client: Option<SharedConfigManagerClient>,
/// STRK/USD oracle for SNIP-35 dynamic gas pricing. The underlying trait is generic; this
/// field's name labels the rate semantics. The instance must be configured with STRK/USD
/// URLs at construction time. None if disabled.
pub strk_to_usd_oracle: Option<Arc<dyn ExchangeRateOracleClientTrait>>,
}

#[derive(thiserror::Error, PartialEq, Debug)]
Expand Down Expand Up @@ -457,23 +453,18 @@ impl SequencerConsensusContext {
};
SNIP35_FEE_ACTUAL.set_lossy(fee_actual.0);

let fee_target = match &self.deps.strk_to_usd_oracle {
Some(oracle) => match oracle.fetch_rate(timestamp).await {
Ok(rate) => {
let target = compute_fee_target(TARGET_ATTO_USD_PER_L2_GAS, rate);
match target {
Some(t) => SNIP35_FEE_TARGET.set_lossy(t.0),
None => warn!("STRK/USD oracle returned zero rate, freezing fee_proposal"),
}
target
}
Err(e) => {
warn!("STRK/USD oracle error: {e:?}, freezing fee_proposal");
None
let fee_target = match self.deps.l1_gas_price_provider.get_strk_to_usd_rate(timestamp).await
{
Ok(rate) => {
let target = compute_fee_target(TARGET_ATTO_USD_PER_L2_GAS, rate);
match target {
Some(t) => SNIP35_FEE_TARGET.set_lossy(t.0),
None => warn!("STRK/USD oracle returned zero rate, freezing fee_proposal"),
}
},
None => {
debug!("No STRK/USD oracle configured, freezing fee_proposal");
target
}
Err(e) => {
warn!("STRK/USD oracle error: {e:?}, freezing fee_proposal");
None
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ use apollo_l1_gas_price_types::errors::{
L1GasPriceClientError,
L1GasPriceProviderError,
};
use apollo_l1_gas_price_types::{
MockExchangeRateOracleClientTrait,
MockL1GasPriceProviderClient,
PriceInfo,
};
use apollo_l1_gas_price_types::{MockL1GasPriceProviderClient, PriceInfo};
use apollo_protobuf::consensus::{
BuildParam,
CommitmentParts,
Expand Down Expand Up @@ -1619,28 +1615,20 @@ async fn test_initialize_fee_proposals_window(

#[derive(Clone)]
enum OracleBehavior {
/// No oracle is configured (`strk_to_usd_oracle = None`).
NotConfigured,
/// Oracle is configured and `fetch_rate` returns `Ok(rate)`.
/// Provider returns `Ok(rate)` for the STRK/USD query.
Ok(u128),
/// Oracle is configured and `fetch_rate` returns `Err(_)`.
/// Provider returns `Err(_)` for the STRK/USD query.
Err,
}

// fee_actual = 10 gwei => margin bounds [9_980_039_920, 10_020_000_000].
#[rstest]
#[case::no_fee_actual_freezes_at_l2_gas_price(
None,
OracleBehavior::NotConfigured,
OracleBehavior::Ok(0),
GasPrice(7_000_000_000),
GasPrice(7_000_000_000)
)]
#[case::no_oracle_freezes_at_fee_actual(
Some(GasPrice(10_000_000_000)),
OracleBehavior::NotConfigured,
GasPrice(7_000_000_000),
GasPrice(10_000_000_000)
)]
#[case::oracle_zero_rate_freezes_at_fee_actual(
Some(GasPrice(10_000_000_000)),
OracleBehavior::Ok(0),
Expand Down Expand Up @@ -1679,22 +1667,21 @@ async fn test_compute_snip35_fee_proposal(
#[case] expected_fee_proposal: GasPrice,
) {
let (mut deps, _network) = create_test_and_network_deps();
deps.setup_default_expectations();
deps.strk_to_usd_oracle = match oracle_behavior {
OracleBehavior::NotConfigured => None,
// Register the specific strk_to_usd_rate expectation BEFORE setup_default_expectations so
// the catch-all default installed there does not shadow it (mockall matches oldest first).
match oracle_behavior {
OracleBehavior::Ok(rate) => {
let mut mock = MockExchangeRateOracleClientTrait::new();
mock.expect_fetch_rate().returning(move |_| Ok(rate));
Some(Arc::new(mock))
deps.l1_gas_price_provider.expect_get_strk_to_usd_rate().returning(move |_| Ok(rate));
}
OracleBehavior::Err => {
let mut mock = MockExchangeRateOracleClientTrait::new();
mock.expect_fetch_rate().returning(|_| {
Err(ExchangeRateOracleClientError::RequestError("test".to_string()))
deps.l1_gas_price_provider.expect_get_strk_to_usd_rate().returning(|_| {
Err(L1GasPriceClientError::ExchangeRateOracleClientError(
ExchangeRateOracleClientError::RequestError("test".to_string()),
))
});
Some(Arc::new(mock))
}
};
}
deps.setup_default_expectations();

let mut context = deps.build_context();
context.l2_gas_price = l2_gas_price;
Expand All @@ -1713,16 +1700,17 @@ async fn test_compute_snip35_fee_proposal_converges_to_oracle_target() {
];

let (mut deps, _network) = create_test_and_network_deps();
deps.setup_default_expectations();
let mut mock = MockExchangeRateOracleClientTrait::new();
// Register per-phase strk_to_usd_rate expectations BEFORE setup_default_expectations so they
// are not shadowed by the catch-all default (mockall matches oldest first).
let mut seq = mockall::Sequence::new();
for &(rate, _, n_blocks) in &phases {
mock.expect_fetch_rate()
deps.l1_gas_price_provider
.expect_get_strk_to_usd_rate()
.times(usize::try_from(n_blocks).unwrap())
.in_sequence(&mut seq)
.returning(move |_| Ok(rate));
}
deps.strk_to_usd_oracle = Some(Arc::new(mock));
deps.setup_default_expectations();
let mut context = deps.build_context();

// Bootstrap the window with 75 gwei (the $0.04 target).
Expand Down
11 changes: 7 additions & 4 deletions crates/apollo_consensus_orchestrator/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ pub(crate) const CHAIN_ID: ChainId = ChainId::Mainnet;
// values here.
pub(crate) const ETH_TO_FRI_RATE: u128 = 2 * u128::pow(10, 18);

// A STRK/USD rate that yields an in-bounds SNIP-35 fee target in proposal-building tests
// where the specific value doesn't matter.
pub(crate) const DEFAULT_STRK_TO_USD_RATE: u128 = 300_000_000_000_000_000;

pub(crate) static TX_BATCH: LazyLock<Vec<ConsensusTransaction>> =
LazyLock::new(|| (0..3).map(generate_invoke_tx).collect());

Expand Down Expand Up @@ -129,8 +133,6 @@ pub(crate) struct TestDeps {
pub clock: Arc<dyn Clock>,
pub outbound_proposal_sender: mpsc::Sender<(HeightAndRound, mpsc::Receiver<ProposalPart>)>,
pub vote_broadcast_client: BroadcastTopicClient<Vote>,
pub strk_to_usd_oracle:
Option<Arc<dyn apollo_l1_gas_price_types::ExchangeRateOracleClientTrait>>,
}

impl From<TestDeps> for SequencerConsensusContextDeps {
Expand All @@ -145,7 +147,6 @@ impl From<TestDeps> for SequencerConsensusContextDeps {
outbound_proposal_sender: deps.outbound_proposal_sender,
vote_broadcast_client: deps.vote_broadcast_client,
config_manager_client: None,
strk_to_usd_oracle: deps.strk_to_usd_oracle,
}
}
}
Expand Down Expand Up @@ -325,6 +326,9 @@ impl TestDeps {
blob_fee: GasPrice(TEMP_ETH_BLOB_GAS_FEE_IN_WEI),
}));
self.l1_gas_price_provider.expect_get_rate().return_const(Ok(ETH_TO_FRI_RATE));
self.l1_gas_price_provider
.expect_get_strk_to_usd_rate()
.return_const(Ok(DEFAULT_STRK_TO_USD_RATE));
}

fn setup_default_state_sync_get_block(&mut self) {
Expand Down Expand Up @@ -381,7 +385,6 @@ pub(crate) fn create_test_and_network_deps() -> (TestDeps, NetworkDependencies)
clock,
outbound_proposal_sender,
vote_broadcast_client: votes_topic_client,
strk_to_usd_oracle: None,
};

let network_deps =
Expand Down
26 changes: 26 additions & 0 deletions crates/apollo_dashboard/resources/dev_grafana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3478,6 +3478,32 @@
],
"extra_params": {}
},
{
"title": "get_strk_to_usd_rate (client-side)",
"description": "client-side infra metrics for request type get_strk_to_usd_rate",
"type": "timeseries",
"exprs": [
"histogram_quantile(0.50,label_replace(sum by (le) (rate(l1_gas_price_local_response_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.50 l1_gas_price_local_response_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.95,label_replace(sum by (le) (rate(l1_gas_price_local_response_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.95 l1_gas_price_local_response_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.50,label_replace(sum by (le) (rate(l1_gas_price_remote_response_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.50 l1_gas_price_remote_response_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.95,label_replace(sum by (le) (rate(l1_gas_price_remote_response_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.95 l1_gas_price_remote_response_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.50,label_replace(sum by (le) (rate(l1_gas_price_remote_client_communication_failure_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.50 l1_gas_price_remote_client_communication_failure_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.95,label_replace(sum by (le) (rate(l1_gas_price_remote_client_communication_failure_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.95 l1_gas_price_remote_client_communication_failure_times_secs\", \"le\", \".*\"))"
],
"extra_params": {}
},
{
"title": "get_strk_to_usd_rate (server-side)",
"description": "server-side infra metrics for request type get_strk_to_usd_rate",
"type": "timeseries",
"exprs": [
"histogram_quantile(0.50,label_replace(sum by (le) (rate(l1_gas_price_processing_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.50 l1_gas_price_processing_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.95,label_replace(sum by (le) (rate(l1_gas_price_processing_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.95 l1_gas_price_processing_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.50,label_replace(sum by (le) (rate(l1_gas_price_queueing_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.50 l1_gas_price_queueing_times_secs\", \"le\", \".*\"))",
"histogram_quantile(0.95,label_replace(sum by (le) (rate(l1_gas_price_queueing_times_secs_bucket{cluster=~\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", request_variant=\"get_strk_to_usd_rate\"}[5m])), \"label_name\", \"0.95 l1_gas_price_queueing_times_secs\", \"le\", \".*\"))"
],
"extra_params": {}
},
{
"title": "initialize (client-side)",
"description": "client-side infra metrics for request type initialize",
Expand Down
6 changes: 6 additions & 0 deletions crates/apollo_integration_tests/src/flow_test_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ impl FlowSequencerSetup {
let (eth_to_strk_oracle_url_headers, _join_handle) =
spawn_local_eth_to_strk_oracle(available_ports.get_next_port());
let eth_to_strk_oracle_config = ExchangeRateOracleConfig {
url_header_list: Some(vec![eth_to_strk_oracle_url_headers.clone().into()]),
..Default::default()
};
// Reuse the same dummy oracle endpoint for STRK/USD; the handler returns a constant.
let strk_to_usd_oracle_config = ExchangeRateOracleConfig {
url_header_list: Some(vec![eth_to_strk_oracle_url_headers.into()]),
..Default::default()
};
Expand Down Expand Up @@ -309,6 +314,7 @@ impl FlowSequencerSetup {
state_sync_config,
consensus_manager_config,
eth_to_strk_oracle_config,
strk_to_usd_oracle_config,
mempool_p2p_config,
monitoring_endpoint_config,
component_config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,11 @@ async fn get_sequencer_setup_configs(
url_header_list: Some(vec![eth_to_strk_oracle_url.clone().into()]),
..Default::default()
};
// Reuse the same dummy oracle endpoint for STRK/USD; the handler returns a constant.
let strk_to_usd_oracle_config = ExchangeRateOracleConfig {
url_header_list: Some(vec![eth_to_strk_oracle_url.clone().into()]),
..Default::default()
};

let validator_id = set_validator_id(&mut consensus_manager_config, node_index);
let chain_info = chain_info.clone();
Expand Down Expand Up @@ -1397,6 +1402,7 @@ async fn get_sequencer_setup_configs(
state_sync_config.clone(),
consensus_manager_config.clone(),
eth_to_strk_oracle_config.clone(),
strk_to_usd_oracle_config.clone(),
mempool_p2p_config.clone(),
monitoring_endpoint_config,
executable_component_config.clone(),
Expand Down
2 changes: 2 additions & 0 deletions crates/apollo_integration_tests/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ pub fn create_node_config(
mut state_sync_config: StateSyncConfig,
mut consensus_manager_config: ConsensusManagerConfig,
eth_to_strk_oracle_config: ExchangeRateOracleConfig,
strk_to_usd_oracle_config: ExchangeRateOracleConfig,
mempool_p2p_config: MempoolP2pConfig,
monitoring_endpoint_config: MonitoringEndpointConfig,
components: ComponentConfig,
Expand Down Expand Up @@ -313,6 +314,7 @@ pub fn create_node_config(
// Use newly minted blocks on Anvil to be used for gas price calculations.
lag_margin_seconds: Duration::from_secs(0),
eth_to_strk_oracle_config,
strk_to_usd_oracle_config,
..Default::default()
};
let http_server_config =
Expand Down
3 changes: 3 additions & 0 deletions crates/apollo_l1_gas_price/src/communication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ impl ComponentRequestHandler<L1GasPriceRequest, L1GasPriceResponse> for L1GasPri
L1GasPriceRequest::GetEthToFriRate(timestamp) => {
L1GasPriceResponse::GetEthToFriRate(self.eth_to_fri_rate(timestamp).await)
}
L1GasPriceRequest::GetStrkToUsdRate(timestamp) => {
L1GasPriceResponse::GetStrkToUsdRate(self.strk_to_usd_rate(timestamp).await)
}
}
}
}
19 changes: 19 additions & 0 deletions crates/apollo_l1_gas_price_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub enum L1GasPriceRequest {
GetGasPrice(BlockTimestamp),
AddGasPrice(GasPriceData),
GetEthToFriRate(u64),
GetStrkToUsdRate(u64),
}
impl_debug_for_infra_requests_and_responses!(L1GasPriceRequest);
impl_labeled_request!(L1GasPriceRequest, L1GasPriceRequestLabelValue);
Expand All @@ -82,6 +83,7 @@ pub enum L1GasPriceResponse {
GetGasPrice(L1GasPriceProviderResult<PriceInfo>),
AddGasPrice(L1GasPriceProviderResult<()>),
GetEthToFriRate(L1GasPriceProviderResult<u128>),
GetStrkToUsdRate(L1GasPriceProviderResult<u128>),
}
impl_debug_for_infra_requests_and_responses!(L1GasPriceResponse);

Expand All @@ -99,7 +101,11 @@ pub trait L1GasPriceProviderClient: Send + Sync {
timestamp: BlockTimestamp,
) -> L1GasPriceProviderClientResult<PriceInfo>;

/// ETH/FRI rate as 18-decimal fixed-point integer (e.g. 5000 STRK per ETH → `5000 * 10^18`).
async fn get_rate(&self, timestamp: u64) -> L1GasPriceProviderClientResult<u128>;

/// STRK/USD rate as 18-decimal fixed-point integer (e.g. 24.5 STRK per USD → `24.5 * 10^18`).
async fn get_strk_to_usd_rate(&self, timestamp: u64) -> L1GasPriceProviderClientResult<u128>;
}

#[cfg_attr(any(feature = "testing", test), automock)]
Expand Down Expand Up @@ -171,6 +177,19 @@ where
Direct
)
}
#[instrument(skip(self))]
async fn get_strk_to_usd_rate(&self, timestamp: u64) -> L1GasPriceProviderClientResult<u128> {
let request = L1GasPriceRequest::GetStrkToUsdRate(timestamp);
handle_all_response_variants!(
self,
request,
L1GasPriceResponse,
GetStrkToUsdRate,
L1GasPriceClientError,
L1GasPriceProviderError,
Direct
)
}
}

generate_permutation_labels! {
Expand Down
Loading