From da5323ca3f6514cb9f958cebecf6bed789fa6baf Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 21 Oct 2024 17:30:23 +0200 Subject: [PATCH 01/12] init --- Cargo.lock | 14 +++ crates/client/eth/Cargo.toml | 1 + crates/client/eth/src/l1_gas_price.rs | 14 ++- crates/client/eth/src/lib.rs | 1 + crates/client/eth/src/oracle.rs | 171 ++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 crates/client/eth/src/oracle.rs diff --git a/Cargo.lock b/Cargo.lock index 7c8b128f0..d7399d195 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1185,6 +1185,19 @@ dependencies = [ "serde", ] +[[package]] +name = "bigdecimal" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits 0.2.19", +] + [[package]] name = "bincode" version = "1.3.3" @@ -5399,6 +5412,7 @@ version = "0.7.0" dependencies = [ "alloy", "anyhow", + "bigdecimal", "bitvec", "blockifier", "dotenv", diff --git a/crates/client/eth/Cargo.toml b/crates/client/eth/Cargo.toml index 3a2aea27d..0d5563268 100644 --- a/crates/client/eth/Cargo.toml +++ b/crates/client/eth/Cargo.toml @@ -35,6 +35,7 @@ starknet_api = { workspace = true } # Other alloy = { workspace = true } anyhow = "1.0.75" +bigdecimal = "0.4.5" bitvec = { workspace = true } blockifier = { workspace = true } futures = { workspace = true, default-features = true } diff --git a/crates/client/eth/src/l1_gas_price.rs b/crates/client/eth/src/l1_gas_price.rs index 0c8394ae8..c67768941 100644 --- a/crates/client/eth/src/l1_gas_price.rs +++ b/crates/client/eth/src/l1_gas_price.rs @@ -1,7 +1,8 @@ -use crate::client::EthereumClient; -use alloy::eips::BlockNumberOrTag; +use crate::{client::EthereumClient, oracle::{self, PragmaV1}}; +use alloy::{eips::BlockNumberOrTag, primitives::U256}; use alloy::providers::Provider; use anyhow::Context; +use bigdecimal::BigDecimal; use mc_mempool::{GasPriceProvider, L1DataProvider}; use std::time::{Duration, UNIX_EPOCH}; @@ -67,6 +68,15 @@ async fn update_gas_price(eth_client: &EthereumClient, l1_gas_provider: GasPrice l1_gas_provider.update_eth_l1_gas_price(*eth_gas_price); l1_gas_provider.update_eth_l1_data_gas_price(avg_blob_base_fee); + //fetch eth/strk price + let oracle = PragmaV1::new(config); + let (eth_strk_price,decimals) = oracle.fetch_eth_strk_price().await.expect("failed to retrieve ETH/STRK price"); + let strk_gas_price = (BigDecimal::new(*eth_gas_price.into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())).as_bigint_and_exponent(); + let strk_data_gas_price = (BigDecimal::new(avg_blob_base_fee.into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())).as_bigint_and_exponent(); + l1_gas_provider.update_strk_l1_gas_price(*eth_gas_price); + // l1_gas_provider.update_strk_l1_data_gas_price(avg_blob_base_fee); + + l1_gas_provider.update_last_update_timestamp(); // Update block number separately to avoid holding the lock for too long diff --git a/crates/client/eth/src/lib.rs b/crates/client/eth/src/lib.rs index 4e8675d38..40e100cb2 100644 --- a/crates/client/eth/src/lib.rs +++ b/crates/client/eth/src/lib.rs @@ -5,3 +5,4 @@ pub mod l1_messaging; pub mod state_update; pub mod sync; pub mod utils; +pub mod oracle; \ No newline at end of file diff --git a/crates/client/eth/src/oracle.rs b/crates/client/eth/src/oracle.rs new file mode 100644 index 000000000..e0418347f --- /dev/null +++ b/crates/client/eth/src/oracle.rs @@ -0,0 +1,171 @@ +use std::fmt; + +use alloy::transports::http::reqwest; +use anyhow::{bail, Ok}; +use serde::{Deserialize, Serialize}; + +pub const DEFAULT_API_URL: &str = "https://api.dev.pragma.build/node/v1/data/"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "oracle_name", content = "config")] +pub enum OracleConfig { + Pragma(PragmaOracle), +} + +impl OracleConfig { + pub fn get_fetch_url(&self, base: String, quote: String) -> String { + match self { + OracleConfig::Pragma(pragma_oracle) => pragma_oracle.get_fetch_url(base, quote), + } + } + + pub fn get_api_key(&self) -> &String { + match self { + OracleConfig::Pragma(oracle) => &oracle.api_key, + } + } + + pub fn is_in_bounds(&self, price: u128) -> bool { + match self { + OracleConfig::Pragma(oracle) => oracle.price_bounds.low <= price && price <= oracle.price_bounds.high, + } + } +} + +impl Default for OracleConfig { + fn default() -> Self { + Self::Pragma(PragmaOracle::default()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PragmaOracle { + #[serde(default = "default_oracle_api_url")] + pub api_url: String, + #[serde(default)] + pub api_key: String, + #[serde(default)] + pub aggregation_method: AggregationMethod, + #[serde(default)] + pub interval: Interval, + #[serde(default)] + pub price_bounds: PriceBounds, +} + +impl Default for PragmaOracle { + fn default() -> Self { + Self { + api_url: default_oracle_api_url(), + api_key: String::default(), + aggregation_method: AggregationMethod::Median, + interval: Interval::OneMinute, + price_bounds: Default::default(), + } + } +} + +impl PragmaOracle { + fn get_fetch_url(&self, base: String, quote: String) -> String { + format!("{}{}/{}?interval={}&aggregation={}", self.api_url, base, quote, self.interval, self.aggregation_method) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +/// Supported Aggregation Methods +pub enum AggregationMethod { + #[serde(rename = "median")] + Median, + #[serde(rename = "mean")] + Mean, + #[serde(rename = "twap")] + #[default] + Twap, +} + +impl fmt::Display for AggregationMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + AggregationMethod::Median => "median", + AggregationMethod::Mean => "mean", + AggregationMethod::Twap => "twap", + }; + write!(f, "{}", name) + } +} + +/// Supported Aggregation Intervals +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub enum Interval { + #[serde(rename = "1min")] + OneMinute, + #[serde(rename = "15min")] + FifteenMinutes, + #[serde(rename = "1h")] + OneHour, + #[serde(rename = "2h")] + #[default] + TwoHours, +} + +impl fmt::Display for Interval { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Interval::OneMinute => "1min", + Interval::FifteenMinutes => "15min", + Interval::OneHour => "1h", + Interval::TwoHours => "2h", + }; + write!(f, "{}", name) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PriceBounds { + pub low: u128, + pub high: u128, +} + +impl Default for PriceBounds { + fn default() -> Self { + Self { low: 0, high: u128::MAX } + } +} + +fn default_oracle_api_url() -> String { + DEFAULT_API_URL.into() +} + +#[derive(Deserialize, Debug)] +struct PragmaApiResponse { + price: String, + decimals: u32, +} + +pub struct PragmaV1 { + config: OracleConfig, +} + +impl PragmaV1 { + pub fn new(config: OracleConfig) -> Self { + Self { + config, + } + } + + pub async fn fetch_eth_strk_price(&self) -> anyhow::Result<(u128, u32)> { + let response = reqwest::Client::new() + .get(self.config.get_fetch_url(String::from("eth"), String::from("strk"))) + .header("x-api-key", self.config.get_api_key()) + .send() + .await + .expect("failed to retrieve price from pragma oracle"); + + let oracle_api_response = response.json::().await.expect("failed to parse api response"); + let eth_strk_price = u128::from_str_radix(oracle_api_response.price.trim_start_matches("0x"), 16)?; + + if !self.config.is_in_bounds(eth_strk_price){ + bail!("ETH/STRK price is out of bound, please check configuration"); + } + Ok((eth_strk_price, oracle_api_response.decimals)) + } +} \ No newline at end of file From bdccab86ad5725529e9b4026db5ba1b86926a7f7 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Thu, 24 Oct 2024 13:52:30 +0200 Subject: [PATCH 02/12] well --- Cargo.lock | 2 + crates/client/eth/src/l1_gas_price.rs | 28 +++--- crates/client/eth/src/lib.rs | 1 - crates/client/mempool/Cargo.toml | 2 + crates/client/mempool/src/l1.rs | 13 +++ crates/client/mempool/src/lib.rs | 1 + crates/client/{eth => mempool}/src/oracle.rs | 91 ++++++++++++-------- crates/node/src/cli/l1.rs | 8 ++ crates/node/src/main.rs | 16 +++- 9 files changed, 113 insertions(+), 49 deletions(-) rename crates/client/{eth => mempool}/src/oracle.rs (70%) diff --git a/Cargo.lock b/Cargo.lock index d7399d195..52c02772c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5517,7 +5517,9 @@ dependencies = [ "mp-utils", "proptest", "proptest-derive", + "reqwest 0.12.8", "rstest 0.18.2", + "serde", "starknet-core 0.11.0", "starknet-types-core", "starknet_api", diff --git a/crates/client/eth/src/l1_gas_price.rs b/crates/client/eth/src/l1_gas_price.rs index c67768941..897c45800 100644 --- a/crates/client/eth/src/l1_gas_price.rs +++ b/crates/client/eth/src/l1_gas_price.rs @@ -1,5 +1,5 @@ -use crate::{client::EthereumClient, oracle::{self, PragmaV1}}; -use alloy::{eips::BlockNumberOrTag, primitives::U256}; +use crate::client::EthereumClient; +use alloy::eips::BlockNumberOrTag; use alloy::providers::Provider; use anyhow::Context; use bigdecimal::BigDecimal; @@ -68,14 +68,22 @@ async fn update_gas_price(eth_client: &EthereumClient, l1_gas_provider: GasPrice l1_gas_provider.update_eth_l1_gas_price(*eth_gas_price); l1_gas_provider.update_eth_l1_data_gas_price(avg_blob_base_fee); - //fetch eth/strk price - let oracle = PragmaV1::new(config); - let (eth_strk_price,decimals) = oracle.fetch_eth_strk_price().await.expect("failed to retrieve ETH/STRK price"); - let strk_gas_price = (BigDecimal::new(*eth_gas_price.into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())).as_bigint_and_exponent(); - let strk_data_gas_price = (BigDecimal::new(avg_blob_base_fee.into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())).as_bigint_and_exponent(); - l1_gas_provider.update_strk_l1_gas_price(*eth_gas_price); - // l1_gas_provider.update_strk_l1_data_gas_price(avg_blob_base_fee); - + // fetch eth/strk price and update + if let Some(oracle_provider) = &l1_gas_provider.oracle_provider { + let (eth_strk_price, decimals) = + oracle_provider.fetch_eth_strk_price().await.expect("failed to retrieve ETH/STRK price"); + let strk_gas_price = (BigDecimal::new((*eth_gas_price).into(), decimals.into()) + / BigDecimal::new(eth_strk_price.into(), decimals.into())) + .as_bigint_and_exponent() + .0; + let strk_data_gas_price = (BigDecimal::new(avg_blob_base_fee.into(), decimals.into()) + / BigDecimal::new(eth_strk_price.into(), decimals.into())) + .as_bigint_and_exponent() + .0; + + l1_gas_provider.update_strk_l1_gas_price(strk_gas_price.to_str_radix(10).parse::()?); + l1_gas_provider.update_strk_l1_data_gas_price(strk_data_gas_price.to_str_radix(10).parse::()?); + } l1_gas_provider.update_last_update_timestamp(); diff --git a/crates/client/eth/src/lib.rs b/crates/client/eth/src/lib.rs index 40e100cb2..4e8675d38 100644 --- a/crates/client/eth/src/lib.rs +++ b/crates/client/eth/src/lib.rs @@ -5,4 +5,3 @@ pub mod l1_messaging; pub mod state_update; pub mod sync; pub mod utils; -pub mod oracle; \ No newline at end of file diff --git a/crates/client/mempool/Cargo.toml b/crates/client/mempool/Cargo.toml index e2f77c51d..555e1a660 100644 --- a/crates/client/mempool/Cargo.toml +++ b/crates/client/mempool/Cargo.toml @@ -56,5 +56,7 @@ starknet_api.workspace = true anyhow.workspace = true log.workspace = true mockall = { workspace = true, optional = true } +reqwest.workspace = true +serde.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/crates/client/mempool/src/l1.rs b/crates/client/mempool/src/l1.rs index 3e7c86211..51d82aa44 100644 --- a/crates/client/mempool/src/l1.rs +++ b/crates/client/mempool/src/l1.rs @@ -3,6 +3,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::SystemTime; +use crate::oracle::Oracle; + #[derive(Clone)] pub struct GasPriceProvider { gas_prices: Arc>, @@ -11,6 +13,7 @@ pub struct GasPriceProvider { data_gas_price_sync_enabled: Arc, strk_gas_price_sync_enabled: Arc, strk_data_gas_price_sync_enabled: Arc, + pub oracle_provider: Option>, } impl GasPriceProvider { @@ -22,9 +25,19 @@ impl GasPriceProvider { data_gas_price_sync_enabled: Arc::new(AtomicBool::new(true)), strk_gas_price_sync_enabled: Arc::new(AtomicBool::new(true)), strk_data_gas_price_sync_enabled: Arc::new(AtomicBool::new(true)), + oracle_provider: None, } } + pub fn is_oracle_needed(&self) -> bool { + self.strk_gas_price_sync_enabled.load(Ordering::Relaxed) + || self.strk_data_gas_price_sync_enabled.load(Ordering::Relaxed) + } + + pub fn set_oracle_provider(&mut self, oracle_provider: Oracle) { + self.oracle_provider = Some(Arc::new(oracle_provider)); + } + pub fn set_gas_prices(&self, new_prices: GasPrices) { self.update_eth_l1_gas_price(new_prices.eth_l1_gas_price); self.update_strk_l1_gas_price(new_prices.strk_l1_gas_price); diff --git a/crates/client/mempool/src/lib.rs b/crates/client/mempool/src/lib.rs index 48b5aadb6..019aa40e6 100644 --- a/crates/client/mempool/src/lib.rs +++ b/crates/client/mempool/src/lib.rs @@ -43,6 +43,7 @@ mod close_block; pub mod header; mod inner; mod l1; +pub mod oracle; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/crates/client/eth/src/oracle.rs b/crates/client/mempool/src/oracle.rs similarity index 70% rename from crates/client/eth/src/oracle.rs rename to crates/client/mempool/src/oracle.rs index e0418347f..0ba4da5f5 100644 --- a/crates/client/eth/src/oracle.rs +++ b/crates/client/mempool/src/oracle.rs @@ -1,6 +1,5 @@ use std::fmt; -use alloy::transports::http::reqwest; use anyhow::{bail, Ok}; use serde::{Deserialize, Serialize}; @@ -8,31 +7,56 @@ pub const DEFAULT_API_URL: &str = "https://api.dev.pragma.build/node/v1/data/"; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "oracle_name", content = "config")] -pub enum OracleConfig { +pub enum Oracle { Pragma(PragmaOracle), } -impl OracleConfig { +impl Oracle { + pub fn new(oracle_name: &str, url: String, key: String) -> anyhow::Result { + match oracle_name { + "Pragma" => Ok(Oracle::Pragma(PragmaOracle::new(url, key))), + _ => bail!("Unknown Oracle name"), + } + } + + pub fn set_base_url(&mut self, url: String) { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.api_url = url, + } + } + + pub async fn fetch_eth_strk_price(&self) -> anyhow::Result<(u128, u32)> { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.fetch_eth_strk_price().await, + } + } + + pub fn set_api_key(&mut self, key: String) { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.api_key = key, + } + } + pub fn get_fetch_url(&self, base: String, quote: String) -> String { match self { - OracleConfig::Pragma(pragma_oracle) => pragma_oracle.get_fetch_url(base, quote), + Oracle::Pragma(pragma_oracle) => pragma_oracle.get_fetch_url(base, quote), } } pub fn get_api_key(&self) -> &String { match self { - OracleConfig::Pragma(oracle) => &oracle.api_key, + Oracle::Pragma(oracle) => &oracle.api_key, } } pub fn is_in_bounds(&self, price: u128) -> bool { match self { - OracleConfig::Pragma(oracle) => oracle.price_bounds.low <= price && price <= oracle.price_bounds.high, + Oracle::Pragma(oracle) => oracle.price_bounds.low <= price && price <= oracle.price_bounds.high, } } } -impl Default for OracleConfig { +impl Default for Oracle { fn default() -> Self { Self::Pragma(PragmaOracle::default()) } @@ -65,9 +89,33 @@ impl Default for PragmaOracle { } impl PragmaOracle { + pub fn new(api_url: String, api_key: String) -> Self { + Self { + api_url, + api_key, + aggregation_method: AggregationMethod::Median, + interval: Interval::OneMinute, + price_bounds: Default::default(), + } + } + fn get_fetch_url(&self, base: String, quote: String) -> String { format!("{}{}/{}?interval={}&aggregation={}", self.api_url, base, quote, self.interval, self.aggregation_method) } + + pub async fn fetch_eth_strk_price(&self) -> anyhow::Result<(u128, u32)> { + let response = reqwest::Client::new() + .get(self.get_fetch_url(String::from("eth"), String::from("strk"))) + .header("x-api-key", self.api_key.clone()) + .send() + .await + .expect("failed to retrieve price from pragma oracle"); + + let oracle_api_response = response.json::().await.expect("failed to parse api response"); + let eth_strk_price = u128::from_str_radix(oracle_api_response.price.trim_start_matches("0x"), 16)?; + + Ok((eth_strk_price, oracle_api_response.decimals)) + } } #[derive(Default, Debug, Serialize, Deserialize, Clone)] @@ -140,32 +188,3 @@ struct PragmaApiResponse { price: String, decimals: u32, } - -pub struct PragmaV1 { - config: OracleConfig, -} - -impl PragmaV1 { - pub fn new(config: OracleConfig) -> Self { - Self { - config, - } - } - - pub async fn fetch_eth_strk_price(&self) -> anyhow::Result<(u128, u32)> { - let response = reqwest::Client::new() - .get(self.config.get_fetch_url(String::from("eth"), String::from("strk"))) - .header("x-api-key", self.config.get_api_key()) - .send() - .await - .expect("failed to retrieve price from pragma oracle"); - - let oracle_api_response = response.json::().await.expect("failed to parse api response"); - let eth_strk_price = u128::from_str_radix(oracle_api_response.price.trim_start_matches("0x"), 16)?; - - if !self.config.is_in_bounds(eth_strk_price){ - bail!("ETH/STRK price is out of bound, please check configuration"); - } - Ok((eth_strk_price, oracle_api_response.decimals)) - } -} \ No newline at end of file diff --git a/crates/node/src/cli/l1.rs b/crates/node/src/cli/l1.rs index 62607b2ab..cd5edf7c0 100644 --- a/crates/node/src/cli/l1.rs +++ b/crates/node/src/cli/l1.rs @@ -30,6 +30,14 @@ pub struct L1SyncParams { #[clap(env = "MADARA_STRK_DATA_GAS_PRICE", long, alias = "strk-blob-gas-price")] pub strk_blob_gas_price: Option, + /// Oracle API url. + #[clap(env = "ORACLE_URL", long, alias = "oracle-url")] + pub oracle_url: Option, + + /// Oracle API key. + #[clap(env = "ORACLE_API_KEY", long, alias = "oracle-api-key")] + pub oracle_api_key: Option, + /// Time in which the gas price worker will fetch the gas price. #[clap( env = "MADARA_GAS_PRICE_POLL", diff --git a/crates/node/src/main.rs b/crates/node/src/main.rs index def1742e8..a97ec34c9 100644 --- a/crates/node/src/main.rs +++ b/crates/node/src/main.rs @@ -12,7 +12,7 @@ use clap::Parser; use cli::{NetworkType, RunCmd}; use mc_block_import::BlockImporter; use mc_db::DatabaseService; -use mc_mempool::{GasPriceProvider, L1DataProvider, Mempool}; +use mc_mempool::{oracle::Oracle, GasPriceProvider, L1DataProvider, Mempool}; use mc_metrics::MetricsService; use mc_rpc::providers::{AddTransactionProvider, ForwardToProvider, MempoolAddTxProvider}; use mc_telemetry::{SysInfo, TelemetryService}; @@ -89,7 +89,7 @@ async fn main() -> anyhow::Result<()> { .context("Initializing importer service")?, ); - let l1_gas_setter = GasPriceProvider::new(); + let mut l1_gas_setter = GasPriceProvider::new(); if let Some(fix_gas) = run_cmd.l1_sync_params.gas_price { l1_gas_setter.update_eth_l1_gas_price(fix_gas as u128); @@ -107,6 +107,18 @@ async fn main() -> anyhow::Result<()> { l1_gas_setter.update_strk_l1_data_gas_price(strk_fix_blob_gas as u128); l1_gas_setter.set_strk_data_gas_price_sync_enabled(false); } + if let Some(ref oracle_url) = run_cmd.l1_sync_params.oracle_url { + if let Some(ref oracle_api_key) = run_cmd.l1_sync_params.oracle_api_key { + let oracle = Oracle::new("Pragma", oracle_url.to_string(), oracle_api_key.clone())?; + l1_gas_setter.set_oracle_provider(oracle); + } + } + + if l1_gas_setter.is_oracle_needed() && l1_gas_setter.oracle_provider.is_none() { + log::error!("STRK gas is not fixed and oracle is not provided"); + panic!(); + } + let l1_data_provider: Arc = Arc::new(l1_gas_setter.clone()); // declare mempool here so that it can be used to process l1->l2 messages in the l1 service From 4634e8a0b67580ae97f5cd5a8c2552d61e7a2015 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Thu, 24 Oct 2024 13:53:34 +0200 Subject: [PATCH 03/12] Update l1_gas_price.rs --- crates/client/eth/src/l1_gas_price.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/client/eth/src/l1_gas_price.rs b/crates/client/eth/src/l1_gas_price.rs index 897c45800..f3346e25c 100644 --- a/crates/client/eth/src/l1_gas_price.rs +++ b/crates/client/eth/src/l1_gas_price.rs @@ -74,15 +74,13 @@ async fn update_gas_price(eth_client: &EthereumClient, l1_gas_provider: GasPrice oracle_provider.fetch_eth_strk_price().await.expect("failed to retrieve ETH/STRK price"); let strk_gas_price = (BigDecimal::new((*eth_gas_price).into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())) - .as_bigint_and_exponent() - .0; + .as_bigint_and_exponent(); let strk_data_gas_price = (BigDecimal::new(avg_blob_base_fee.into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())) - .as_bigint_and_exponent() - .0; + .as_bigint_and_exponent(); - l1_gas_provider.update_strk_l1_gas_price(strk_gas_price.to_str_radix(10).parse::()?); - l1_gas_provider.update_strk_l1_data_gas_price(strk_data_gas_price.to_str_radix(10).parse::()?); + l1_gas_provider.update_strk_l1_gas_price(strk_gas_price.0.to_str_radix(10).parse::()?); + l1_gas_provider.update_strk_l1_data_gas_price(strk_data_gas_price.0.to_str_radix(10).parse::()?); } l1_gas_provider.update_last_update_timestamp(); From 8a8fa6b009d46d3f9976a9783b6e2c67f5f67403 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Thu, 24 Oct 2024 13:55:36 +0200 Subject: [PATCH 04/12] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d9f4f49..611d914e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Next release +- feat: fetch eth/strk price and sync strk gas price - feat: declare v0, l1 handler support added - feat: strk gas price cli param added - fix(snos): added special address while closing block for SNOS From d83b8f976b8cfea2c06e020f439d44ebc39e7c45 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Thu, 24 Oct 2024 14:13:03 +0200 Subject: [PATCH 05/12] moved oracle to a primitive --- Cargo.lock | 13 ++++ Cargo.toml | 1 + crates/client/mempool/Cargo.toml | 1 + crates/client/mempool/src/l1.rs | 3 +- crates/client/mempool/src/lib.rs | 1 - crates/node/Cargo.toml | 1 + crates/node/src/main.rs | 3 +- crates/primitives/oracle/Cargo.toml | 24 +++++++ crates/primitives/oracle/src/lib.rs | 63 +++++++++++++++++++ .../oracle/src/pragma.rs} | 60 +----------------- 10 files changed, 107 insertions(+), 63 deletions(-) create mode 100644 crates/primitives/oracle/Cargo.toml create mode 100644 crates/primitives/oracle/src/lib.rs rename crates/{client/mempool/src/oracle.rs => primitives/oracle/src/pragma.rs} (69%) diff --git a/Cargo.lock b/Cargo.lock index 52c02772c..5ee31c545 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5261,6 +5261,7 @@ dependencies = [ "mp-block", "mp-chain-config", "mp-convert", + "mp-oracle", "mp-utils", "primitive-types", "rand", @@ -5511,6 +5512,7 @@ dependencies = [ "mp-chain-config", "mp-class", "mp-convert", + "mp-oracle", "mp-receipt", "mp-state-update", "mp-transactions", @@ -5790,6 +5792,17 @@ dependencies = [ "starknet-types-core", ] +[[package]] +name = "mp-oracle" +version = "0.7.0" +dependencies = [ + "anyhow", + "num-bigint", + "reqwest 0.12.8", + "serde", + "serde_json", +] + [[package]] name = "mp-receipt" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index ae7018dab..0d1e639be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ mp-receipt = { path = "crates/primitives/receipt", default-features = false } mp-state-update = { path = "crates/primitives/state_update", default-features = false } mp-utils = { path = "crates/primitives/utils", default-features = false } mp-chain-config = { path = "crates/primitives/chain_config", default-features = false } +mp-oracle = { path = "crates/primitives/oracle", default-features = false } # Madara client mc-telemetry = { path = "crates/client/telemetry" } diff --git a/crates/client/mempool/Cargo.toml b/crates/client/mempool/Cargo.toml index 555e1a660..1f1e37c97 100644 --- a/crates/client/mempool/Cargo.toml +++ b/crates/client/mempool/Cargo.toml @@ -45,6 +45,7 @@ mp-receipt.workspace = true mp-state-update.workspace = true mp-transactions.workspace = true mp-utils.workspace = true +mp-oracle.workspace = true # Starknet blockifier.workspace = true diff --git a/crates/client/mempool/src/l1.rs b/crates/client/mempool/src/l1.rs index 51d82aa44..d985e8726 100644 --- a/crates/client/mempool/src/l1.rs +++ b/crates/client/mempool/src/l1.rs @@ -1,10 +1,9 @@ use mp_block::header::{GasPrices, L1DataAvailabilityMode}; +use mp_oracle::Oracle; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::SystemTime; -use crate::oracle::Oracle; - #[derive(Clone)] pub struct GasPriceProvider { gas_prices: Arc>, diff --git a/crates/client/mempool/src/lib.rs b/crates/client/mempool/src/lib.rs index 019aa40e6..48b5aadb6 100644 --- a/crates/client/mempool/src/lib.rs +++ b/crates/client/mempool/src/lib.rs @@ -43,7 +43,6 @@ mod close_block; pub mod header; mod inner; mod l1; -pub mod oracle; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 108723f39..2442b3021 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -35,6 +35,7 @@ mp-block.workspace = true mp-chain-config.workspace = true mp-convert.workspace = true mp-utils.workspace = true +mp-oracle.workspace = true # Starknet blockifier.workspace = true diff --git a/crates/node/src/main.rs b/crates/node/src/main.rs index a97ec34c9..b5965f101 100644 --- a/crates/node/src/main.rs +++ b/crates/node/src/main.rs @@ -12,11 +12,12 @@ use clap::Parser; use cli::{NetworkType, RunCmd}; use mc_block_import::BlockImporter; use mc_db::DatabaseService; -use mc_mempool::{oracle::Oracle, GasPriceProvider, L1DataProvider, Mempool}; +use mc_mempool::{GasPriceProvider, L1DataProvider, Mempool}; use mc_metrics::MetricsService; use mc_rpc::providers::{AddTransactionProvider, ForwardToProvider, MempoolAddTxProvider}; use mc_telemetry::{SysInfo, TelemetryService}; use mp_convert::ToFelt; +use mp_oracle::Oracle; use mp_utils::service::{Service, ServiceGroup}; use service::{BlockProductionService, GatewayService, L1SyncService, RpcService, SyncService}; use starknet_providers::SequencerGatewayProvider; diff --git a/crates/primitives/oracle/Cargo.toml b/crates/primitives/oracle/Cargo.toml new file mode 100644 index 000000000..43065b49e --- /dev/null +++ b/crates/primitives/oracle/Cargo.toml @@ -0,0 +1,24 @@ +[package] +description = "Madara primitive for Oracles" +name = "mp-oracle" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true +homepage.workspace = true + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] + +# Other +num-bigint = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +anyhow = { workspace = true } +reqwest = { workspace = true } \ No newline at end of file diff --git a/crates/primitives/oracle/src/lib.rs b/crates/primitives/oracle/src/lib.rs new file mode 100644 index 000000000..40aa9319b --- /dev/null +++ b/crates/primitives/oracle/src/lib.rs @@ -0,0 +1,63 @@ +use anyhow::bail; +use serde::{Deserialize, Serialize}; + +mod pragma; + +use pragma::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "oracle_name", content = "config")] +pub enum Oracle { + Pragma(PragmaOracle), +} + +impl Oracle { + pub fn new(oracle_name: &str, url: String, key: String) -> anyhow::Result { + match oracle_name { + "Pragma" => Ok(Oracle::Pragma(PragmaOracle::new(url, key))), + _ => bail!("Unknown Oracle name"), + } + } + + pub fn set_base_url(&mut self, url: String) { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.api_url = url, + } + } + + pub async fn fetch_eth_strk_price(&self) -> anyhow::Result<(u128, u32)> { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.fetch_eth_strk_price().await, + } + } + + pub fn set_api_key(&mut self, key: String) { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.api_key = key, + } + } + + pub fn get_fetch_url(&self, base: String, quote: String) -> String { + match self { + Oracle::Pragma(pragma_oracle) => pragma_oracle.get_fetch_url(base, quote), + } + } + + pub fn get_api_key(&self) -> &String { + match self { + Oracle::Pragma(oracle) => &oracle.api_key, + } + } + + pub fn is_in_bounds(&self, price: u128) -> bool { + match self { + Oracle::Pragma(oracle) => oracle.price_bounds.low <= price && price <= oracle.price_bounds.high, + } + } +} + +impl Default for Oracle { + fn default() -> Self { + Self::Pragma(PragmaOracle::default()) + } +} diff --git a/crates/client/mempool/src/oracle.rs b/crates/primitives/oracle/src/pragma.rs similarity index 69% rename from crates/client/mempool/src/oracle.rs rename to crates/primitives/oracle/src/pragma.rs index 0ba4da5f5..0f0bfb4aa 100644 --- a/crates/client/mempool/src/oracle.rs +++ b/crates/primitives/oracle/src/pragma.rs @@ -1,67 +1,9 @@ use std::fmt; -use anyhow::{bail, Ok}; use serde::{Deserialize, Serialize}; pub const DEFAULT_API_URL: &str = "https://api.dev.pragma.build/node/v1/data/"; -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "oracle_name", content = "config")] -pub enum Oracle { - Pragma(PragmaOracle), -} - -impl Oracle { - pub fn new(oracle_name: &str, url: String, key: String) -> anyhow::Result { - match oracle_name { - "Pragma" => Ok(Oracle::Pragma(PragmaOracle::new(url, key))), - _ => bail!("Unknown Oracle name"), - } - } - - pub fn set_base_url(&mut self, url: String) { - match self { - Oracle::Pragma(pragma_oracle) => pragma_oracle.api_url = url, - } - } - - pub async fn fetch_eth_strk_price(&self) -> anyhow::Result<(u128, u32)> { - match self { - Oracle::Pragma(pragma_oracle) => pragma_oracle.fetch_eth_strk_price().await, - } - } - - pub fn set_api_key(&mut self, key: String) { - match self { - Oracle::Pragma(pragma_oracle) => pragma_oracle.api_key = key, - } - } - - pub fn get_fetch_url(&self, base: String, quote: String) -> String { - match self { - Oracle::Pragma(pragma_oracle) => pragma_oracle.get_fetch_url(base, quote), - } - } - - pub fn get_api_key(&self) -> &String { - match self { - Oracle::Pragma(oracle) => &oracle.api_key, - } - } - - pub fn is_in_bounds(&self, price: u128) -> bool { - match self { - Oracle::Pragma(oracle) => oracle.price_bounds.low <= price && price <= oracle.price_bounds.high, - } - } -} - -impl Default for Oracle { - fn default() -> Self { - Self::Pragma(PragmaOracle::default()) - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PragmaOracle { #[serde(default = "default_oracle_api_url")] @@ -99,7 +41,7 @@ impl PragmaOracle { } } - fn get_fetch_url(&self, base: String, quote: String) -> String { + pub fn get_fetch_url(&self, base: String, quote: String) -> String { format!("{}{}/{}?interval={}&aggregation={}", self.api_url, base, quote, self.interval, self.aggregation_method) } From 1f0373b64f22a932cfdf154701632bf567d861f4 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Thu, 24 Oct 2024 14:39:59 +0200 Subject: [PATCH 06/12] lint --- crates/client/mempool/Cargo.toml | 2 +- crates/node/Cargo.toml | 2 +- crates/primitives/oracle/Cargo.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/client/mempool/Cargo.toml b/crates/client/mempool/Cargo.toml index 1f1e37c97..4f3a55f07 100644 --- a/crates/client/mempool/Cargo.toml +++ b/crates/client/mempool/Cargo.toml @@ -41,11 +41,11 @@ mp-block.workspace = true mp-chain-config.workspace = true mp-class.workspace = true mp-convert.workspace = true +mp-oracle.workspace = true mp-receipt.workspace = true mp-state-update.workspace = true mp-transactions.workspace = true mp-utils.workspace = true -mp-oracle.workspace = true # Starknet blockifier.workspace = true diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 2442b3021..1a11fadaa 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -34,8 +34,8 @@ mc-telemetry.workspace = true mp-block.workspace = true mp-chain-config.workspace = true mp-convert.workspace = true -mp-utils.workspace = true mp-oracle.workspace = true +mp-utils.workspace = true # Starknet blockifier.workspace = true diff --git a/crates/primitives/oracle/Cargo.toml b/crates/primitives/oracle/Cargo.toml index 43065b49e..9f5c8c7e0 100644 --- a/crates/primitives/oracle/Cargo.toml +++ b/crates/primitives/oracle/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # Other +anyhow = { workspace = true } num-bigint = { workspace = true } +reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -anyhow = { workspace = true } -reqwest = { workspace = true } \ No newline at end of file From cecb08143ce1d297d4b4fa435466477961dc5802 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 28 Oct 2024 16:11:03 +0100 Subject: [PATCH 07/12] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 308c200e5..c85e447a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## Next release - - feat: fetch eth/strk price and sync strk gas price - fix(rocksdb): update max open files opt - code: refactor to use otel tracing instead of prometheus (removed mc-metrics, added mc-analytics) From 066680e5f33e6c6c3ca855fda7b2832c24b0bd2e Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 28 Oct 2024 16:15:11 +0100 Subject: [PATCH 08/12] Update Cargo.toml --- crates/node/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 9c2fb7171..0a866c2a8 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -28,15 +28,14 @@ mc-devnet = { workspace = true } mc-eth = { workspace = true } mc-gateway = { workspace = true } mc-mempool = { workspace = true } -mc-metrics = { workspace = true } mc-rpc = { workspace = true } mc-sync = { workspace = true } mc-telemetry = { workspace = true } mp-block = { workspace = true } mp-chain-config = { workspace = true } mp-convert = { workspace = true } -mp-utils = { workspace = true } mp-oracle = { workspace = true } +mp-utils = { workspace = true } # Starknet blockifier.workspace = true From 694c99536182f77062c89ef8c87e68807540d3ad Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 28 Oct 2024 18:18:44 +0100 Subject: [PATCH 09/12] Update starknet-js-test.yml --- .github/workflows/starknet-js-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/starknet-js-test.yml b/.github/workflows/starknet-js-test.yml index 1feb0c4b3..8f2bc2898 100644 --- a/.github/workflows/starknet-js-test.yml +++ b/.github/workflows/starknet-js-test.yml @@ -26,7 +26,7 @@ jobs: fail-on-cache-miss: true - name: Setup dev chain and run tests run: | - ./target/release/madara --name madara --base-path ../madara_db --rpc-port 9944 --rpc-cors "*" --rpc-external --devnet --preset devnet --gas-price 0 --blob-gas-price 0 --no-l1-sync & + ./target/release/madara --name madara --base-path ../madara_db --rpc-port 9944 --rpc-cors "*" --rpc-external --devnet --preset devnet --gas-price 0 --blob-gas-price 0 --strk-gas-price 0 --strk-blob-gas-price 0 --no-l1-sync & MADARA_PID=$! while ! echo exit | nc localhost 9944; do sleep 1; done cd tests/js_tests From 589d040f3ec8cb2adb96abec91b92d0a34a795d9 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 28 Oct 2024 19:03:12 +0100 Subject: [PATCH 10/12] fix bug from old pr --- crates/client/mempool/src/l1.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/client/mempool/src/l1.rs b/crates/client/mempool/src/l1.rs index d985e8726..fd3e2fc2e 100644 --- a/crates/client/mempool/src/l1.rs +++ b/crates/client/mempool/src/l1.rs @@ -53,11 +53,11 @@ impl GasPriceProvider { } pub fn set_strk_gas_price_sync_enabled(&self, enabled: bool) { - self.gas_price_sync_enabled.store(enabled, Ordering::Relaxed); + self.strk_gas_price_sync_enabled.store(enabled, Ordering::Relaxed); } pub fn set_strk_data_gas_price_sync_enabled(&self, enabled: bool) { - self.data_gas_price_sync_enabled.store(enabled, Ordering::Relaxed); + self.strk_data_gas_price_sync_enabled.store(enabled, Ordering::Relaxed); } pub fn update_last_update_timestamp(&self) { From 5d2321415d20440dffd8c112d3434c1f75fded58 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 4 Nov 2024 14:53:08 +0100 Subject: [PATCH 11/12] changed expect to context --- crates/client/eth/src/l1_gas_price.rs | 2 +- crates/primitives/oracle/src/pragma.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/client/eth/src/l1_gas_price.rs b/crates/client/eth/src/l1_gas_price.rs index 346031be4..361ec2648 100644 --- a/crates/client/eth/src/l1_gas_price.rs +++ b/crates/client/eth/src/l1_gas_price.rs @@ -71,7 +71,7 @@ async fn update_gas_price(eth_client: &EthereumClient, l1_gas_provider: GasPrice // fetch eth/strk price and update if let Some(oracle_provider) = &l1_gas_provider.oracle_provider { let (eth_strk_price, decimals) = - oracle_provider.fetch_eth_strk_price().await.expect("failed to retrieve ETH/STRK price"); + oracle_provider.fetch_eth_strk_price().await.context("failed to retrieve ETH/STRK price")?; let strk_gas_price = (BigDecimal::new((*eth_gas_price).into(), decimals.into()) / BigDecimal::new(eth_strk_price.into(), decimals.into())) .as_bigint_and_exponent(); diff --git a/crates/primitives/oracle/src/pragma.rs b/crates/primitives/oracle/src/pragma.rs index 0f0bfb4aa..4c0be73a1 100644 --- a/crates/primitives/oracle/src/pragma.rs +++ b/crates/primitives/oracle/src/pragma.rs @@ -1,5 +1,6 @@ use std::fmt; +use anyhow::Context; use serde::{Deserialize, Serialize}; pub const DEFAULT_API_URL: &str = "https://api.dev.pragma.build/node/v1/data/"; @@ -51,9 +52,9 @@ impl PragmaOracle { .header("x-api-key", self.api_key.clone()) .send() .await - .expect("failed to retrieve price from pragma oracle"); + .context("failed to retrieve price from pragma oracle")?; - let oracle_api_response = response.json::().await.expect("failed to parse api response"); + let oracle_api_response = response.json::().await.context("failed to parse api response")?; let eth_strk_price = u128::from_str_radix(oracle_api_response.price.trim_start_matches("0x"), 16)?; Ok((eth_strk_price, oracle_api_response.decimals)) From 2428bac55fb5f55a05e4f5af256ff0c9cc7b7ac3 Mon Sep 17 00:00:00 2001 From: azurwastaken Date: Mon, 4 Nov 2024 18:07:19 +0100 Subject: [PATCH 12/12] Update pragma.rs --- crates/primitives/oracle/src/pragma.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/primitives/oracle/src/pragma.rs b/crates/primitives/oracle/src/pragma.rs index 4c0be73a1..a5c8ac445 100644 --- a/crates/primitives/oracle/src/pragma.rs +++ b/crates/primitives/oracle/src/pragma.rs @@ -1,6 +1,6 @@ use std::fmt; -use anyhow::Context; +use anyhow::{bail, Context}; use serde::{Deserialize, Serialize}; pub const DEFAULT_API_URL: &str = "https://api.dev.pragma.build/node/v1/data/"; @@ -56,7 +56,9 @@ impl PragmaOracle { let oracle_api_response = response.json::().await.context("failed to parse api response")?; let eth_strk_price = u128::from_str_radix(oracle_api_response.price.trim_start_matches("0x"), 16)?; - + if eth_strk_price == 0 { + bail!("Pragma api returned 0 for eth/strk price"); + } Ok((eth_strk_price, oracle_api_response.decimals)) } }