Skip to content

Commit

Permalink
feat: add anvil_setNextBlockBaseFeePerGas (#450)
Browse files Browse the repository at this point in the history
* slightly refactor `TestNodeFeeInputProvider`

* add `anvil_setNextBlockBaseFeePerGas`

* run `yarn fmt:fix`
  • Loading branch information
itegulov authored Nov 29, 2024
1 parent 5aa6e15 commit 6e6dba2
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 51 deletions.
1 change: 1 addition & 0 deletions SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The `status` options are:

| Namespace | API | <div style="width:130px">Status</div> | Description |
| --- | --- | --- | --- |
| `ANVIL` | `anvil_setNextBlockBaseFeePerGas` | `SUPPORTED` | Sets the base fee of the next block |
| `ANVIL` | `anvil_dropTransaction` | `SUPPORTED` | Removes a transaction from the pool |
| `ANVIL` | `anvil_dropAllTransactions` | `SUPPORTED` | Remove all transactions from the pool |
| `ANVIL` | `anvil_removePoolTransactions` | `SUPPORTED` | Remove all transactions from the pool by sender address |
Expand Down
38 changes: 38 additions & 0 deletions e2e-tests/test/anvil-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,44 @@ import * as fs from "node:fs";

const provider = getTestProvider();

describe("anvil_setNextBlockBaseFeePerGas", function () {
it("Should change gas price", async function () {
const oldBaseFee = (await provider.getGasPrice()).toNumber();
const expectedNewBaseFee = oldBaseFee + 42;

// Act
await provider.send("anvil_setNextBlockBaseFeePerGas", [ethers.utils.hexlify(expectedNewBaseFee)]);

// Assert
const newBaseFee = (await provider.getGasPrice()).toNumber();
expect(newBaseFee).to.equal(expectedNewBaseFee);

// Revert
await provider.send("anvil_setNextBlockBaseFeePerGas", [ethers.utils.hexlify(oldBaseFee)]);
});

it("Should produce a block with new gas price", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey, provider);
const userWallet = Wallet.createRandom().connect(provider);
const oldBaseFee = (await provider.getGasPrice()).toNumber();
const expectedNewBaseFee = oldBaseFee + 42;

// Act
await provider.send("anvil_setNextBlockBaseFeePerGas", [ethers.utils.hexlify(expectedNewBaseFee)]);

const txResponse = await wallet.sendTransaction({
to: userWallet.address,
value: ethers.utils.parseEther("0.1"),
});
const txReceipt = await txResponse.wait();
const newBlock = await provider.getBlock(txReceipt.blockNumber);
expect(newBlock.baseFeePerGas?.toNumber()).to.equal(expectedNewBaseFee);

// Revert
await provider.send("anvil_setNextBlockBaseFeePerGas", [ethers.utils.hexlify(oldBaseFee)]);
});
});

describe("anvil_setBlockTimestampInterval & anvil_removeBlockTimestampInterval", function () {
it("Should control timestamp interval between blocks", async function () {
// Arrange
Expand Down
8 changes: 8 additions & 0 deletions src/namespaces/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ use crate::utils::Numeric;

#[rpc]
pub trait AnvilNamespaceT {
/// Sets the base fee of the next block.
///
/// # Arguments
///
/// * `base_fee` - Value to be set as base fee for the next block
#[rpc(name = "anvil_setNextBlockBaseFeePerGas")]
fn set_next_block_base_fee_per_gas(&self, base_fee: U256) -> RpcResult<()>;

/// Removes a transaction from the pool.
///
/// # Arguments
Expand Down
9 changes: 9 additions & 0 deletions src/node/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ use crate::{
impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> AnvilNamespaceT
for InMemoryNode<S>
{
fn set_next_block_base_fee_per_gas(&self, base_fee: U256) -> RpcResult<()> {
self.set_next_block_base_fee_per_gas(base_fee)
.map_err(|err| {
tracing::error!("failed setting next block's base fee: {:?}", err);
into_jsrpc_error(Web3Error::InternalError(err))
})
.into_boxed_future()
}

fn drop_transaction(&self, hash: H256) -> RpcResult<Option<H256>> {
self.drop_transaction(hash)
.map_err(|err| {
Expand Down
10 changes: 5 additions & 5 deletions src/node/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
&self,
tx: zksync_types::transaction_request::CallRequest,
) -> Result<H256, Web3Error> {
let (chain_id, l1_gas_price) = {
let (chain_id, l2_gas_price) = {
let reader = self
.inner
.read()
.map_err(|_| anyhow::anyhow!("Failed to acquire read lock"))?;
(
reader.fork_storage.chain_id,
reader.fee_input_provider.l1_gas_price,
reader.fee_input_provider.gas_price(),
)
};

Expand All @@ -136,7 +136,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
return Err(TransparentError(err.into()).into());
}
} else {
tx_req.gas_price = tx.max_fee_per_gas.unwrap_or(U256::from(l1_gas_price));
tx_req.gas_price = tx.max_fee_per_gas.unwrap_or(U256::from(l2_gas_price));
tx_req.max_priority_fee_per_gas = tx.max_priority_fee_per_gas;
if tx_req.transaction_type.is_none() {
tx_req.transaction_type = Some(zksync_types::EIP_1559_TX_TYPE.into());
Expand Down Expand Up @@ -676,7 +676,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa
.read()
.expect("Failed to acquire read lock")
.fee_input_provider
.l2_gas_price;
.gas_price();
Ok(U256::from(fair_l2_gas_price)).into_boxed_future()
}

Expand Down Expand Up @@ -1402,7 +1402,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa
.clamp(1, reader.current_miniblock + 1);

let mut base_fee_per_gas =
vec![U256::from(reader.fee_input_provider.l2_gas_price); block_count as usize];
vec![U256::from(reader.fee_input_provider.gas_price()); block_count as usize];

let oldest_block = reader.current_miniblock + 1 - base_fee_per_gas.len() as u64;
// We do not store gas used ratio for blocks, returns array of zeroes as a placeholder.
Expand Down
118 changes: 76 additions & 42 deletions src/node/fee_model.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
use zksync_types::fee_model::{BatchFeeInput, FeeModelConfigV2, FeeParams, FeeParamsV2};
use zksync_multivm::utils::derive_base_fee_and_gas_per_pubdata;
use zksync_multivm::VmVersion;
use zksync_types::fee_model::{
BaseTokenConversionRatio, BatchFeeInput, FeeModelConfigV2, FeeParams, FeeParamsV2,
};

use crate::config::constants::{
DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
DEFAULT_FAIR_PUBDATA_PRICE, DEFAULT_L1_GAS_PRICE, DEFAULT_L2_GAS_PRICE,
};
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct TestNodeFeeInputProvider {
pub l1_gas_price: u64,
pub l1_pubdata_price: u64,
pub l2_gas_price: u64,
pub compute_overhead_part: f64,
pub pubdata_overhead_part: f64,
pub batch_overhead_l1_gas: u64,
pub max_gas_per_batch: u64,
pub max_pubdata_per_batch: u64,
/// L1 Gas Price Scale Factor for gas estimation.
pub estimate_gas_price_scale_factor: f64,
/// The factor by which to scale the gasLimit.
pub estimate_gas_scale_factor: f32,

fee_params: FeeParamsV2,
forced_base_fee: Option<u64>,
}

// TODO: Derive PartialEq for `FeeParamsV2` in upstream
impl PartialEq for TestNodeFeeInputProvider {
fn eq(&self, other: &Self) -> bool {
fn eq_config(a: FeeModelConfigV2, b: FeeModelConfigV2) -> bool {
a.minimal_l2_gas_price == b.minimal_l2_gas_price
&& a.compute_overhead_part == b.compute_overhead_part
&& a.pubdata_overhead_part == b.pubdata_overhead_part
&& a.batch_overhead_l1_gas == b.batch_overhead_l1_gas
&& a.max_gas_per_batch == b.max_gas_per_batch
&& a.max_pubdata_per_batch == b.max_pubdata_per_batch
}

self.estimate_gas_price_scale_factor == other.estimate_gas_price_scale_factor
&& self.estimate_gas_scale_factor == other.estimate_gas_scale_factor
&& self.fee_params.l1_gas_price() == other.fee_params.l1_gas_price()
&& self.fee_params.l1_pubdata_price() == other.fee_params.l1_pubdata_price()
&& eq_config(self.fee_params.config(), other.fee_params.config())
}
}

impl TestNodeFeeInputProvider {
Expand All @@ -29,16 +48,10 @@ impl TestNodeFeeInputProvider {
match fee_params {
FeeParams::V1(_) => todo!(),
FeeParams::V2(fee_params) => Self {
l1_gas_price: fee_params.l1_gas_price(),
l1_pubdata_price: fee_params.l1_pubdata_price(),
l2_gas_price: fee_params.config().minimal_l2_gas_price,
compute_overhead_part: fee_params.config().compute_overhead_part,
pubdata_overhead_part: fee_params.config().pubdata_overhead_part,
batch_overhead_l1_gas: fee_params.config().batch_overhead_l1_gas,
max_gas_per_batch: fee_params.config().max_gas_per_batch,
max_pubdata_per_batch: fee_params.config().max_pubdata_per_batch,
estimate_gas_price_scale_factor,
estimate_gas_scale_factor,
fee_params,
forced_base_fee: None,
},
}
}
Expand All @@ -55,49 +68,70 @@ impl TestNodeFeeInputProvider {
}

pub fn get_fee_model_config(&self) -> FeeModelConfigV2 {
FeeModelConfigV2 {
minimal_l2_gas_price: self.l2_gas_price,
compute_overhead_part: self.compute_overhead_part,
pubdata_overhead_part: self.pubdata_overhead_part,
batch_overhead_l1_gas: self.batch_overhead_l1_gas,
max_gas_per_batch: self.max_gas_per_batch,
max_pubdata_per_batch: self.max_pubdata_per_batch,
}
self.fee_params.config()
}

fn get_params(&self) -> FeeParams {
// TODO: consider using old fee model for the olds blocks, when forking
FeeParams::V2(FeeParamsV2::new(
self.get_fee_model_config(),
self.l1_gas_price,
self.l1_pubdata_price,
Default::default(),
))
FeeParams::V2(self.fee_params)
}

fn enforce_base_fee(&self, mut fee_input: BatchFeeInput) -> BatchFeeInput {
if let Some(base_fee) = self.forced_base_fee {
let mut pubdata_fee_input = fee_input.into_pubdata_independent();
pubdata_fee_input.fair_l2_gas_price = base_fee;
fee_input = BatchFeeInput::PubdataIndependent(pubdata_fee_input);
}
fee_input
}

pub(crate) fn get_batch_fee_input(&self) -> BatchFeeInput {
self.get_params().scale(1.0, 1.0)
let fee_input = self.get_params().scale(1.0, 1.0);
self.enforce_base_fee(fee_input)
}

pub(crate) fn get_batch_fee_input_scaled(&self) -> BatchFeeInput {
let scale_factor = self.estimate_gas_price_scale_factor;
self.get_params().scale(scale_factor, scale_factor)
let fee_input = self.get_params().scale(scale_factor, scale_factor);
self.enforce_base_fee(fee_input)
}

pub fn gas_price(&self) -> u64 {
let (base_fee, _) = derive_base_fee_and_gas_per_pubdata(
self.get_batch_fee_input_scaled(),
VmVersion::latest(),
);
base_fee
}

pub fn fair_pubdata_price(&self) -> u64 {
self.get_batch_fee_input_scaled().fair_pubdata_price()
}

pub fn set_base_fee(&mut self, base_fee: u64) {
self.forced_base_fee = Some(base_fee);
}
}

impl Default for TestNodeFeeInputProvider {
fn default() -> Self {
Self {
l1_gas_price: DEFAULT_L1_GAS_PRICE,
l1_pubdata_price: DEFAULT_FAIR_PUBDATA_PRICE,
l2_gas_price: DEFAULT_L2_GAS_PRICE,
compute_overhead_part: 0.0,
pubdata_overhead_part: 1.0,
batch_overhead_l1_gas: 800000,
max_gas_per_batch: 200000000,
max_pubdata_per_batch: 500000,
estimate_gas_price_scale_factor: DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
estimate_gas_scale_factor: DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
fee_params: FeeParamsV2::new(
FeeModelConfigV2 {
minimal_l2_gas_price: DEFAULT_L2_GAS_PRICE,
compute_overhead_part: 0.0,
pubdata_overhead_part: 1.0,
batch_overhead_l1_gas: 800000,
max_gas_per_batch: 200000000,
max_pubdata_per_batch: 500000,
},
DEFAULT_L1_GAS_PRICE,
DEFAULT_FAIR_PUBDATA_PRICE,
BaseTokenConversionRatio::default(),
),
forced_base_fee: None,
}
}
}
4 changes: 2 additions & 2 deletions src/node/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1430,7 +1430,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
.read()
.expect("failed acquiring reader")
.fee_input_provider
.l2_gas_price;
.gas_price();
if tx.common_data.fee.max_fee_per_gas < l2_gas_price.into() {
tracing::info!(
"Submitted Tx is Unexecutable {:?} because of MaxFeePerGasTooLow {}",
Expand Down Expand Up @@ -1706,7 +1706,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
} else {
U64::from(1)
},
effective_gas_price: Some(inner.fee_input_provider.l2_gas_price.into()),
effective_gas_price: Some(inner.fee_input_provider.gas_price().into()),
transaction_type: Some((transaction_type as u32).into()),
logs_bloom: Default::default(),
};
Expand Down
9 changes: 9 additions & 0 deletions src/node/in_memory_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,15 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
self.pool.drop_transactions_by_sender(address);
Ok(())
}

pub fn set_next_block_base_fee_per_gas(&self, base_fee: U256) -> Result<()> {
self.inner
.write()
.expect("")
.fee_input_provider
.set_base_fee(base_fee.as_u64());
Ok(())
}
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions src/node/zks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,8 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> ZksNamespa
execute_tx_hash: None,
executed_at: None,
l1_gas_price: 0,
l2_fair_gas_price: reader.fee_input_provider.l2_gas_price,
fair_pubdata_price: Some(reader.fee_input_provider.l1_pubdata_price),
l2_fair_gas_price: reader.fee_input_provider.gas_price(),
fair_pubdata_price: Some(reader.fee_input_provider.fair_pubdata_price()),
base_system_contracts_hashes: reader
.system_contracts
.baseline_contracts
Expand Down

0 comments on commit 6e6dba2

Please sign in to comment.