Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gas_adjuster): Use eth_feeHistory for both base fee and blobs #2322

Merged
merged 4 commits into from
Jun 27, 2024
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
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>,
popzxc marked this conversation as resolved.
Show resolved Hide resolved
/// 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> {
popzxc marked this conversation as resolved.
Show resolved Hide resolved
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)
slowli marked this conversation as resolved.
Show resolved Hide resolved
{
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,
popzxc marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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
Loading