Skip to content

Commit

Permalink
feat(gas_adjuster): Use eth_feeHistory for both base fee and blobs (#…
Browse files Browse the repository at this point in the history
…2322)

## What ❔

Updated the codebase to support blob information in the `eth_feeHistory`
RPC method.
Changes GasAdjuster so that it only uses this method to retrieve info.

## Why ❔

Use dedicated RPC method for getting info instead of custom
implementation.
Less requests to L1.
Less code to maintain.

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `zk fmt` and `zk lint`.
  • Loading branch information
popzxc authored Jun 27, 2024
1 parent 85386d3 commit 9985c26
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 214 deletions.
16 changes: 14 additions & 2 deletions core/lib/basic_types/src/web3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,21 +827,33 @@ pub enum TransactionCondition {
}

// `FeeHistory`: from `web3::types::fee_history`
// Adapted to support blobs.

/// The fee history type returned from `eth_feeHistory` call.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeHistory {
/// Lowest number block of the returned range.
pub oldest_block: BlockNumber,
/// A vector of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.
/// A vector of block base fees per gas. This includes the next block after the newest of the returned range,
/// because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.
#[serde(default)] // some node implementations skip empty lists
pub base_fee_per_gas: Vec<U256>,
/// A vector of block gas used ratios. These are calculated as the ratio of gas used and gas limit.
#[serde(default)] // some node implementations skip empty lists
pub gas_used_ratio: Vec<f64>,
/// A vector of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty. Returned only if requested.
/// A vector of effective priority fee per gas data points from a single block. All zeroes are returned if
/// the block is empty. Returned only if requested.
pub reward: Option<Vec<Vec<U256>>>,
/// An array of base fees per blob gas for blocks. This includes the next block following the newest in the
/// returned range, as this value can be derived from the latest block. For blocks before EIP-4844, zeroes
/// are returned.
#[serde(default)] // some node implementations skip empty lists
pub base_fee_per_blob_gas: Vec<U256>,
/// An array showing the ratios of blob gas used in blocks. These ratios are calculated by dividing blobGasUsed
/// by the maximum blob gas per block.
#[serde(default)] // some node implementations skip empty lists
pub blob_gas_used_ratio: Vec<f64>,
}

// `SyncInfo`, `SyncState`: from `web3::types::sync_state`
Expand Down
39 changes: 35 additions & 4 deletions core/lib/eth_client/src/clients/http/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use zksync_web3_decl::error::{ClientRpcContext, EnrichedClientError, EnrichedCli
use super::{decl::L1EthNamespaceClient, Method, COUNTERS, LATENCIES};
use crate::{
types::{ExecutedTxStatus, FailureInfo},
EthInterface, RawTransactionBytes,
BaseFees, EthInterface, RawTransactionBytes,
};

#[async_trait]
Expand Down Expand Up @@ -78,7 +78,15 @@ where
&self,
upto_block: usize,
block_count: usize,
) -> EnrichedClientResult<Vec<u64>> {
) -> EnrichedClientResult<Vec<BaseFees>> {
// Non-panicking conversion to u64.
fn cast_to_u64(value: U256, tag: &str) -> EnrichedClientResult<u64> {
u64::try_from(value).map_err(|_| {
let err = ClientError::Custom(format!("{tag} value does not fit in u64"));
EnrichedClientError::new(err, "cast_to_u64").with_arg("value", &value)
})
}

const MAX_REQUEST_CHUNK: usize = 1024;

COUNTERS.call[&(Method::BaseFeeHistory, self.component())].inc();
Expand All @@ -103,11 +111,34 @@ where
.with_arg("chunk_size", &chunk_size)
.with_arg("block", &chunk_end)
.await?;
history.extend(fee_history.base_fee_per_gas);

// Check that the lengths are the same.
// Per specification, the values should always be provided, and must be 0 for blocks
// prior to EIP-4844.
// https://ethereum.github.io/execution-apis/api-documentation/
if fee_history.base_fee_per_gas.len() != fee_history.base_fee_per_blob_gas.len() {
tracing::error!(
"base_fee_per_gas and base_fee_per_blob_gas have different lengths: {} and {}",
fee_history.base_fee_per_gas.len(),
fee_history.base_fee_per_blob_gas.len()
);
}

for (base, blob) in fee_history
.base_fee_per_gas
.into_iter()
.zip(fee_history.base_fee_per_blob_gas)
{
let fees = BaseFees {
base_fee_per_gas: cast_to_u64(base, "base_fee_per_gas")?,
base_fee_per_blob_gas: blob,
};
history.push(fees)
}
}

latency.observe();
Ok(history.into_iter().map(|fee| fee.as_u64()).collect())
Ok(history)
}

async fn get_pending_block_base_fee_per_gas(&self) -> EnrichedClientResult<U256> {
Expand Down
76 changes: 40 additions & 36 deletions core/lib/eth_client/src/clients/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use zksync_web3_decl::client::{DynClient, MockClient, L1};

use crate::{
types::{ContractCallError, SignedCallResult, SigningError},
BoundEthInterface, Options, RawTransactionBytes,
BaseFees, BoundEthInterface, Options, RawTransactionBytes,
};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -212,8 +212,7 @@ type CallHandler =
pub struct MockEthereumBuilder {
max_fee_per_gas: U256,
max_priority_fee_per_gas: U256,
base_fee_history: Vec<u64>,
excess_blob_gas_history: Vec<u64>,
base_fee_history: Vec<BaseFees>,
/// If true, the mock will not check the ordering nonces of the transactions.
/// This is useful for testing the cases when the transactions are executed out of order.
non_ordering_confirmations: bool,
Expand All @@ -228,7 +227,6 @@ impl fmt::Debug for MockEthereumBuilder {
.field("max_fee_per_gas", &self.max_fee_per_gas)
.field("max_priority_fee_per_gas", &self.max_priority_fee_per_gas)
.field("base_fee_history", &self.base_fee_history)
.field("excess_blob_gas_history", &self.excess_blob_gas_history)
.field(
"non_ordering_confirmations",
&self.non_ordering_confirmations,
Expand All @@ -244,7 +242,6 @@ impl Default for MockEthereumBuilder {
max_fee_per_gas: 100.into(),
max_priority_fee_per_gas: 10.into(),
base_fee_history: vec![],
excess_blob_gas_history: vec![],
non_ordering_confirmations: false,
inner: Arc::default(),
call_handler: Box::new(|call, block_id| {
Expand All @@ -256,21 +253,13 @@ impl Default for MockEthereumBuilder {

impl MockEthereumBuilder {
/// Sets fee history for each block in the mocked Ethereum network, starting from the 0th block.
pub fn with_fee_history(self, history: Vec<u64>) -> Self {
pub fn with_fee_history(self, history: Vec<BaseFees>) -> Self {
Self {
base_fee_history: history,
..self
}
}

/// Sets the excess blob gas history for each block in the mocked Ethereum network, starting from the 0th block.
pub fn with_excess_blob_gas_history(self, history: Vec<u64>) -> Self {
Self {
excess_blob_gas_history: history,
..self
}
}

pub fn with_non_ordering_confirmation(self, non_ordering_confirmations: bool) -> Self {
Self {
non_ordering_confirmations,
Expand Down Expand Up @@ -306,19 +295,16 @@ impl MockEthereumBuilder {
}

fn get_block_by_number(
base_fee_history: &[u64],
excess_blob_gas_history: &[u64],
fee_history: &[BaseFees],
block: web3::BlockNumber,
) -> Option<web3::Block<H256>> {
let web3::BlockNumber::Number(number) = block else {
panic!("Non-numeric block requested");
};
let excess_blob_gas = excess_blob_gas_history
.get(number.as_usize())
.map(|excess_blob_gas| (*excess_blob_gas).into());
let base_fee_per_gas = base_fee_history
let excess_blob_gas = Some(0.into()); // Not relevant for tests.
let base_fee_per_gas = fee_history
.get(number.as_usize())
.map(|base_fee| (*base_fee).into());
.map(|fees| fees.base_fee_per_gas.into());

Some(web3::Block {
number: Some(number),
Expand All @@ -341,18 +327,12 @@ impl MockEthereumBuilder {
move || Ok(U64::from(inner.read().unwrap().block_number))
})
.method("eth_getBlockByNumber", {
let base_fee_history = self.base_fee_history;
let excess_blob_gas_history = self.excess_blob_gas_history;
move |number, full_transactions: bool| {
assert!(
!full_transactions,
"getting blocks with transactions is not mocked"
);
Ok(Self::get_block_by_number(
&base_fee_history,
&excess_blob_gas_history,
number,
))
Ok(Self::get_block_by_number(&self.base_fee_history, number))
}
})
.method("eth_getTransactionCount", {
Expand All @@ -374,10 +354,14 @@ impl MockEthereumBuilder {
oldest_block: start_block.into(),
base_fee_per_gas: base_fee_history[start_block..=from_block]
.iter()
.copied()
.map(U256::from)
.map(|fee| U256::from(fee.base_fee_per_gas))
.collect(),
gas_used_ratio: vec![], // not used
base_fee_per_blob_gas: base_fee_history[start_block..=from_block]
.iter()
.map(|fee| fee.base_fee_per_blob_gas)
.collect(),
gas_used_ratio: vec![], // not used
blob_gas_used_ratio: vec![], // not used
reward: None,
})
},
Expand Down Expand Up @@ -591,10 +575,23 @@ mod tests {
use super::*;
use crate::{CallFunctionArgs, EthInterface};

fn base_fees(block: u64, blob: u64) -> BaseFees {
BaseFees {
base_fee_per_gas: block,
base_fee_per_blob_gas: U256::from(blob),
}
}

#[tokio::test]
async fn managing_block_number() {
let mock = MockEthereum::builder()
.with_fee_history(vec![0, 1, 2, 3, 4])
.with_fee_history(vec![
base_fees(0, 4),
base_fees(1, 3),
base_fees(2, 2),
base_fees(3, 1),
base_fees(4, 0),
])
.build();
let block_number = mock.client.block_number().await.unwrap();
assert_eq!(block_number, 0.into());
Expand Down Expand Up @@ -625,17 +622,24 @@ mod tests {

#[tokio::test]
async fn managing_fee_history() {
let initial_fee_history = vec![
base_fees(1, 4),
base_fees(2, 3),
base_fees(3, 2),
base_fees(4, 1),
base_fees(5, 0),
];
let client = MockEthereum::builder()
.with_fee_history(vec![1, 2, 3, 4, 5])
.with_fee_history(initial_fee_history.clone())
.build();
client.advance_block_number(4);

let fee_history = client.as_ref().base_fee_history(4, 4).await.unwrap();
assert_eq!(fee_history, [2, 3, 4, 5]);
assert_eq!(fee_history, &initial_fee_history[1..=4]);
let fee_history = client.as_ref().base_fee_history(2, 2).await.unwrap();
assert_eq!(fee_history, [2, 3]);
assert_eq!(fee_history, &initial_fee_history[1..=2]);
let fee_history = client.as_ref().base_fee_history(3, 2).await.unwrap();
assert_eq!(fee_history, [3, 4]);
assert_eq!(fee_history, &initial_fee_history[2..=3]);
}

#[tokio::test]
Expand Down
9 changes: 8 additions & 1 deletion core/lib/eth_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ impl Options {
}
}

/// Information about the base fees provided by the L1 client.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct BaseFees {
pub base_fee_per_gas: u64,
pub base_fee_per_blob_gas: U256,
}

/// Common Web3 interface, as seen by the core applications.
/// Encapsulates the raw Web3 interaction, providing a high-level interface. Acts as an extension
/// trait implemented for L1 / Ethereum [clients](zksync_web3_decl::client::Client).
Expand Down Expand Up @@ -96,7 +103,7 @@ pub trait EthInterface: Sync + Send {
&self,
from_block: usize,
block_count: usize,
) -> EnrichedClientResult<Vec<u64>>;
) -> EnrichedClientResult<Vec<BaseFees>>;

/// Returns the `base_fee_per_gas` value for the currently pending L1 block.
async fn get_pending_block_base_fee_per_gas(&self) -> EnrichedClientResult<U256>;
Expand Down
6 changes: 6 additions & 0 deletions core/node/api_server/src/web3/namespaces/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,13 +688,19 @@ impl EthNamespace {
base_fee_per_gas.len()
]);

// We do not support EIP-4844, but per API specification we should return 0 for pre EIP-4844 blocks.
let base_fee_per_blob_gas = vec![U256::zero(); base_fee_per_gas.len()];
let blob_gas_used_ratio = vec![0.0; base_fee_per_gas.len()];

// `base_fee_per_gas` for next L2 block cannot be calculated, appending last fee as a placeholder.
base_fee_per_gas.push(*base_fee_per_gas.last().unwrap());
Ok(FeeHistory {
oldest_block: web3::BlockNumber::Number(oldest_block.into()),
base_fee_per_gas,
gas_used_ratio,
reward,
base_fee_per_blob_gas,
blob_gas_used_ratio,
})
}

Expand Down
21 changes: 16 additions & 5 deletions core/node/eth_sender/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use zksync_config::{
};
use zksync_contracts::BaseSystemContractsHashes;
use zksync_dal::{Connection, ConnectionPool, Core, CoreDal};
use zksync_eth_client::clients::MockEthereum;
use zksync_eth_client::{clients::MockEthereum, BaseFees};
use zksync_l1_contract_interface::i_executor::methods::{ExecuteBatches, ProveBatches};
use zksync_node_fee_model::l1_gas_price::GasAdjuster;
use zksync_node_test_utils::{create_l1_batch, l1_batch_metadata_to_commitment_artifacts};
Expand Down Expand Up @@ -130,12 +130,23 @@ impl EthSenderTester {
..eth_sender_config.clone().sender.unwrap()
};

let history: Vec<_> = history
.into_iter()
.map(|base_fee_per_gas| BaseFees {
base_fee_per_gas,
base_fee_per_blob_gas: 0.into(),
})
.collect();

let gateway = MockEthereum::builder()
.with_fee_history(
std::iter::repeat(0)
.take(Self::WAIT_CONFIRMATIONS as usize)
.chain(history)
.collect(),
std::iter::repeat_with(|| BaseFees {
base_fee_per_gas: 0,
base_fee_per_blob_gas: 0.into(),
})
.take(Self::WAIT_CONFIRMATIONS as usize)
.chain(history)
.collect(),
)
.with_non_ordering_confirmation(non_ordering_confirmations)
.with_call_handler(move |call, _| {
Expand Down
Loading

0 comments on commit 9985c26

Please sign in to comment.