Skip to content
This repository has been archived by the owner on Aug 2, 2024. It is now read-only.

feat: L1 gas price/fix #1624

Closed
wants to merge 16 commits into from
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## v0.8.0

- feat: L1 gas price/fix
- feat: Declare V0 RPC call
- feat: add `TransactionFilter<TxType>` to pallet-starknet `Config`
- chore: remove `ignore` from
`storage_changes_should_revert_on_transaction_revert` test
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/client/l1-gas-price/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct FeeHistory {
/// of the returned range, because this value can be derived from the newest block. Zeroes
/// are returned for pre-EIP-4844 blocks.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub base_fee_per_blob_gas: Vec<u128>,
pub base_fee_per_blob_gas: Vec<String>,
/// An array of block blob gas used ratios. These are calculated as the ratio of gasUsed and
/// gasLimit.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
Expand Down
20 changes: 15 additions & 5 deletions crates/client/l1-gas-price/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ async fn update_gas_price(
// The RPC responds with 301 elements for some reason. It's also just safer to manually
// take the last 300. We choose 300 to get average gas caprice for last one hour (300 * 12 sec block
// time).
let (_, blob_fee_history_one_hour) =
fee_history.result.base_fee_per_blob_gas.split_at(fee_history.result.base_fee_per_blob_gas.len() - 300);
let (_, blob_fee_history_one_hour) = fee_history
.result
.base_fee_per_blob_gas
.split_at(fee_history.result.base_fee_per_blob_gas.len().max(300) - 300);

let avg_blob_base_fee = blob_fee_history_one_hour.iter().sum::<u128>() / blob_fee_history_one_hour.len() as u128;
let avg_blob_base_fee =
blob_fee_history_one_hour.iter().map(|hex_str| u128::from_str_radix(&hex_str[2..], 16).unwrap()).sum::<u128>()
/ blob_fee_history_one_hour.len() as u128;

let eth_gas_price = u128::from_str_radix(
fee_history
Expand All @@ -84,17 +88,20 @@ async fn update_gas_price(
16,
)?;

log::info!("avg_blob_base_fee : {:?}", avg_blob_base_fee);
log::info!("eth_gas_price : {:?}", eth_gas_price);

// TODO: fetch this from the oracle
let eth_strk_price = 2425;

let mut gas_price = gas_price.lock().await;
gas_price.eth_l1_gas_price =
NonZeroU128::new(eth_gas_price).ok_or(format_err!("Failed to convert `eth_gas_price` to NonZeroU128"))?;
gas_price.eth_l1_data_gas_price = NonZeroU128::new(avg_blob_base_fee)
gas_price.eth_l1_data_gas_price = NonZeroU128::new(avg_blob_base_fee + 1)
.ok_or(format_err!("Failed to convert `eth_l1_data_gas_price` to NonZeroU128"))?;
gas_price.strk_l1_gas_price = NonZeroU128::new(eth_gas_price.saturating_mul(eth_strk_price))
.ok_or(format_err!("Failed to convert `strk_l1_gas_price` to NonZeroU128"))?;
gas_price.strk_l1_data_gas_price = NonZeroU128::new(avg_blob_base_fee.saturating_mul(eth_strk_price))
gas_price.strk_l1_data_gas_price = NonZeroU128::new((avg_blob_base_fee + 1).saturating_mul(eth_strk_price))
.ok_or(format_err!("Failed to convert `strk_l1_data_gas_price` to NonZeroU128"))?;
gas_price.last_update_timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_millis();
// explicitly dropping gas price here to avoid long waits when fetching the value
Expand All @@ -103,3 +110,6 @@ async fn update_gas_price(

Ok(())
}

#[tokio::test]
async fn test_l1_gas_price() {}
32 changes: 32 additions & 0 deletions crates/client/rpc-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
#[cfg(test)]
mod tests;

use blockifier::transaction::transactions::DeclareTransaction;
use indexmap::IndexMap;
use jsonrpsee::core::RpcResult;
use jsonrpsee::proc_macros::rpc;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use starknet_api::core::ClassHash;
use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType};
use starknet_api::transaction::{DeclareTransactionV0V1, TransactionHash};

pub mod utils;

Expand All @@ -36,11 +41,38 @@ pub struct PredeployedAccountWithBalance {
pub balance: FieldElement,
}

#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeclareV0Result {
pub txn_hash: TransactionHash,
pub class_hash: ClassHash,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct CustomDeclareV0Transaction {
pub declare_transaction: DeclareTransactionV0V1,
pub program_vec: Vec<u8>,
pub entrypoints: IndexMap<EntryPointType, Vec<EntryPoint>>,
pub abi_length: usize,
}

#[derive(PartialEq, Eq, Debug)]
pub enum DeclareTransactionWithV0 {
V0(Box<DeclareTransactionV0V1>, Vec<u8>, IndexMap<EntryPointType, Vec<EntryPoint>>, usize),
V1(DeclareTransaction),
}

/// Madara rpc interface for additional features.
#[rpc(server, namespace = "madara")]
pub trait MadaraRpcApi: StarknetReadRpcApi {
#[method(name = "predeployedAccounts")]
fn predeployed_accounts(&self) -> RpcResult<Vec<PredeployedAccountWithBalance>>;

// There is an issue in deserialisation when we try to send the class info directly
// That's why we are sending the components saperately here and then building the
// transaction here in madara and executing the function in the pallet.
#[method(name = "declareV0")]
async fn declare_v0_contract(&self, params: CustomDeclareV0Transaction) -> RpcResult<DeclareV0Result>;
}

/// Starknet write rpc interface.
Expand Down
1 change: 1 addition & 0 deletions crates/client/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ sc-network-sync = { workspace = true }
# Starknet
blockifier = { workspace = true }
cairo-vm = { workspace = true }
indexmap = { workspace = true }
jsonrpsee = { workspace = true, features = ["server", "macros"] }
log = { workspace = true }
mp-block = { workspace = true }
Expand Down
134 changes: 113 additions & 21 deletions crates/client/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;

use blockifier::execution::contract_class::{ClassInfo, ContractClassV0, ContractClassV0Inner};
use blockifier::transaction::account_transaction::AccountTransaction;
use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo};
use blockifier::transaction::transactions::L1HandlerTransaction;
use blockifier::transaction::transactions::{DeclareTransaction, L1HandlerTransaction};
use cairo_vm::types::program::Program;
use errors::StarknetRpcApiError;
use jsonrpsee::core::{async_trait, RpcResult};
use log::error;
use mc_genesis_data_provider::GenesisProvider;
pub use mc_rpc_core::utils::*;
use mc_rpc_core::{DeclareTransactionWithV0, DeclareV0Result};
pub use mc_rpc_core::{
Felt, MadaraRpcApiServer, PredeployedAccountWithBalance, StarknetReadRpcApiServer, StarknetTraceRpcApiServer,
StarknetWriteRpcApiServer,
Expand Down Expand Up @@ -50,10 +53,11 @@ use sp_api::ProvideRuntimeApi;
use sp_arithmetic::traits::UniqueSaturatedInto;
use sp_blockchain::HeaderBackend;
use sp_core::H256;
use sp_runtime::codec::DecodeAll;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use sp_runtime::transaction_validity::InvalidTransaction;
use starknet_api::core::Nonce;
use starknet_api::hash::StarkFelt;
use starknet_api::core::{ClassHash, Nonce};
use starknet_api::hash::{StarkFelt, StarkHash};
use starknet_api::transaction::{Calldata, Fee, TransactionHash, TransactionVersion};
use starknet_core::types::{
BlockHashAndNumber, BlockId, BlockStatus, BlockTag, BlockWithTxHashes, BlockWithTxs, BroadcastedDeclareTransaction,
Expand Down Expand Up @@ -220,9 +224,96 @@ where
}
}

impl<A, B, BE, G, C, P, H> Starknet<A, B, BE, G, C, P, H>
where
A: ChainApi<Block = B> + 'static,
B: BlockT,
P: TransactionPool<Block = B> + 'static,
BE: Backend<B> + 'static,
C: HeaderBackend<B> + BlockBackend<B> + StorageProvider<B, BE> + 'static,
C: ProvideRuntimeApi<B>,
C::Api: StarknetRuntimeApi<B> + ConvertTransactionRuntimeApi<B>,
G: GenesisProvider + Send + Sync + 'static,
H: HasherT + Send + Sync + 'static,
{
async fn declare_txn_common(
&self,
transaction_inputs: DeclareTransactionWithV0,
) -> Result<(TransactionHash, ClassHash), StarknetRpcApiError> {
match transaction_inputs {
DeclareTransactionWithV0::V1(transaction) => self._declare_tx(transaction).await,
DeclareTransactionWithV0::V0(declare_txn, program_vec, entrypoints, abi_length) => {
let txn_hash: TransactionHash = TransactionHash(StarkHash { 0: FieldElement::ONE.to_bytes_be() });

let program_decoded = Program::decode_all(&mut &*program_vec).unwrap();

let class_info;
let declare_transaction;

let try_class_info = ClassInfo::new(
&blockifier::execution::contract_class::ContractClass::V0(ContractClassV0(Arc::from(
ContractClassV0Inner { program: program_decoded, entry_points_by_type: entrypoints },
))),
0,
abi_length,
);

match try_class_info {
Ok(val) => {
class_info = val;
}
Err(_e) => return Err(StarknetRpcApiError::InternalServerError),
}

let try_declare_transaction = DeclareTransaction::new(
starknet_api::transaction::DeclareTransaction::V0(*declare_txn),
txn_hash,
class_info,
);

match try_declare_transaction {
Ok(val) => {
declare_transaction = val;
}
Err(e) => return Err(StarknetRpcApiError::from(e)),
}

self._declare_tx(declare_transaction).await
}
}
}

async fn _declare_tx(&self, txn: DeclareTransaction) -> Result<(TransactionHash, ClassHash), StarknetRpcApiError> {
let best_block_hash = self.get_best_block_hash();
let current_block_hash = self.get_best_block_hash();
let contract_class = self
.overrides
.for_block_hash(self.client.as_ref(), current_block_hash)
.contract_class_by_class_hash(current_block_hash, txn.class_hash());

if let Some(contract_class) = contract_class {
error!("Contract class already exists: {:?}", contract_class);
return Err(StarknetRpcApiError::ClassAlreadyDeclared);
}

let extrinsic =
self.convert_tx_to_extrinsic(best_block_hash, AccountTransaction::Declare(txn.clone())).unwrap();

let res = submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await;

match res {
Ok(_val) => {
Ok((txn.tx.compute_hash(Felt252Wrapper::from(self.chain_id().unwrap().0), false), txn.class_hash()))
}
Err(e) => Err(e),
}
}
}

/// Taken from https://github.com/paritytech/substrate/blob/master/client/rpc/src/author/mod.rs#L78
const TX_SOURCE: TransactionSource = TransactionSource::External;

#[async_trait]
impl<A, B, BE, G, C, P, H> MadaraRpcApiServer for Starknet<A, B, BE, G, C, P, H>
where
A: ChainApi<Block = B> + 'static,
Expand Down Expand Up @@ -262,6 +353,22 @@ where
})
.collect::<Vec<_>>())
}

async fn declare_v0_contract(&self, params: mc_rpc_core::CustomDeclareV0Transaction) -> RpcResult<DeclareV0Result> {
let try_declare = self
.declare_txn_common(DeclareTransactionWithV0::V0(
Box::from(params.declare_transaction),
params.program_vec,
params.entrypoints,
params.abi_length,
))
.await;

match try_declare {
Ok((txn_hash, class_hash)) => Ok(DeclareV0Result { class_hash, txn_hash }),
Err(e) => Err(e.into()),
}
}
}

#[async_trait]
Expand Down Expand Up @@ -290,8 +397,6 @@ where
&self,
declare_transaction: BroadcastedDeclareTransaction,
) -> RpcResult<DeclareTransactionResult> {
let best_block_hash = self.get_best_block_hash();

let opt_sierra_contract_class = if let BroadcastedDeclareTransaction::V2(ref tx) = declare_transaction {
Some(flattened_sierra_to_sierra_contract_class(tx.contract_class.clone()))
} else {
Expand All @@ -301,25 +406,12 @@ where
let chain_id = Felt252Wrapper(self.chain_id()?.0);

let transaction = try_declare_tx_from_broadcasted_declare_tx(declare_transaction, chain_id).map_err(|e| {
error!("Failed to convert BroadcastedDeclareTransaction to DeclareTransaction, error: {e}");
error!("Failed to convert Broadcasted DeclareTransaction to DeclareTransaction, error: {e}");
StarknetRpcApiError::InternalServerError
})?;
let (class_hash, tx_hash) = (transaction.class_hash(), transaction.tx_hash());

let current_block_hash = self.get_best_block_hash();
let contract_class = self
.overrides
.for_block_hash(self.client.as_ref(), current_block_hash)
.contract_class_by_class_hash(current_block_hash, class_hash);

if let Some(contract_class) = contract_class {
error!("Contract class already exists: {:?}", contract_class);
return Err(StarknetRpcApiError::ClassAlreadyDeclared.into());
}

let extrinsic = self.convert_tx_to_extrinsic(best_block_hash, AccountTransaction::Declare(transaction))?;

submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?;
let (tx_hash, class_hash) =
self.declare_txn_common(mc_rpc_core::DeclareTransactionWithV0::V1(transaction)).await.unwrap();

if let Some(sierra_contract_class) = opt_sierra_contract_class {
if let Some(e) = self.backend.sierra_classes().store_sierra_class(class_hash, sierra_contract_class).err() {
Expand Down
6 changes: 6 additions & 0 deletions crates/client/rpc/src/starknetrpcwrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Arc;

use jsonrpsee::core::{async_trait, RpcResult};
use mc_genesis_data_provider::GenesisProvider;
use mc_rpc_core::DeclareV0Result;
pub use mc_rpc_core::{
Felt, MadaraRpcApiServer, PredeployedAccountWithBalance, StarknetReadRpcApiServer, StarknetTraceRpcApiServer,
StarknetWriteRpcApiServer,
Expand Down Expand Up @@ -36,6 +37,7 @@ impl<A: ChainApi, B: BlockT, BE, G, C, P, H> Clone for StarknetRpcWrapper<A, B,
}
}

#[async_trait]
impl<A, B, BE, G, C, P, H> MadaraRpcApiServer for StarknetRpcWrapper<A, B, BE, G, C, P, H>
where
A: ChainApi<Block = B> + 'static,
Expand All @@ -51,6 +53,10 @@ where
fn predeployed_accounts(&self) -> RpcResult<Vec<PredeployedAccountWithBalance>> {
self.0.predeployed_accounts()
}

async fn declare_v0_contract(&self, params: mc_rpc_core::CustomDeclareV0Transaction) -> RpcResult<DeclareV0Result> {
self.0.declare_v0_contract(params).await
}
}

#[async_trait]
Expand Down
12 changes: 12 additions & 0 deletions crates/client/rpc/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use std::num::ParseIntError;
use std::{fmt, u64};

use indexmap::IndexMap;
use mp_felt::Felt252Wrapper;
use serde::{Deserialize, Serialize};
use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType};
use starknet_api::transaction::DeclareTransactionV0V1;
use starknet_ff::FieldElement;

pub struct RpcEventFilter {
Expand All @@ -25,6 +29,14 @@ pub enum ParseTokenError {
ParseFailed(ParseIntError),
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct CustomDeclareV0Transaction {
pub declare_transaction: DeclareTransactionV0V1,
pub program_vec: Vec<u8>,
pub entrypoints: IndexMap<EntryPointType, Vec<EntryPoint>>,
pub abi_length: usize,
}

impl fmt::Display for ContinuationToken {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:x},{:x}", self.block_n, self.event_n)
Expand Down
Loading
Loading