Skip to content

Commit

Permalink
feat: Anvil Cancun support (#7242)
Browse files Browse the repository at this point in the history
* feat(anvil-core): EIP4844 variant support

* chore: proper support when converting txs

* feat: add more type support

* chore: lock

* feat: missing type conversions, decoding test

* use correct eip check

* force no blob hashes for eip1559

* feat: support sidecar with 4844 types

* fmt

* feat: turn on c-kzg revm feature

* chore: add new invalid tx errors

* feat: execution validation steps

* feat: enable c-kzg

* feat: use main branch for consensus, update

* chore: rename

* lockfile

* fmt

* fmt

* fmt

* clippy

* feat: update blob fees

* set current blob excess gas and price when creating block

* blob gas checks

* clippy

* chore: remove unneeded fns

* chore: handle fee history

* chore: add excess blob gas and price to feehistory cache

* chore: remove unused

* chore: properly sum cumulative blob gas

* chore: rewrite validation checks

* chore: handle eip4844 variant when decoding

* max blob validation check

* chore: correct and rename blob fee capp err

* feat: fee history response changes

* docs

* several fee fixes

* chore: set blob gas used on rpc response

* fix: use primitives types

* fix: satisfy clippy

* feat(anvil/tests): can_send_eip4844_transaction - fails

* use sidecar builder in tests

* fix: tx_req_to_typed

* nits

* fix: return `blob_gas_price` and `blob_gas_used` in tx receipt

* nits

* fix: gas_price calc in backend::tx_build and nits

* feat(anvil-tests): `can_send_multiple_blobs_in_one_tx`, `cannot_exceed_six_blobs`

* nits

* fix: eip4844 test

* nits

Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>

* feat(anvil-test): 4844 - test should fail.

* fix(anvil): check MAX_BLOB_GAS_PER_BLOCK in tx executor

* nits

* fix: blob error handling

* nits

* type nits

* nit

---------

Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com>
Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
  • Loading branch information
3 people authored May 2, 2024
1 parent 97186b5 commit 7a676f8
Show file tree
Hide file tree
Showing 16 changed files with 613 additions and 51 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions crates/anvil/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ foundry-evm.workspace = true
bytes = "1.4.0"
k256.workspace = true
ethers = { workspace = true, features = ["rustls", "ws", "ipc", "optimism"] }
revm = { workspace = true, features = [
"std",
"serde",
"memory_limit",
"c-kzg",
] }
alloy-primitives = { workspace = true, features = ["serde"] }
alloy-consensus = { workspace = true, features = ["k256", "kzg"] }
alloy-contract = { workspace = true, features = ["pubsub"] }
Expand Down
1 change: 1 addition & 0 deletions crates/anvil/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ revm = { workspace = true, default-features = false, features = [
"std",
"serde",
"memory_limit",
"c-kzg",
] }

alloy-primitives = { workspace = true, features = ["serde"] }
Expand Down
12 changes: 9 additions & 3 deletions crates/anvil/core/src/eth/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ impl Block {
extra_data: partial_header.extra_data,
mix_hash: partial_header.mix_hash,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
blob_gas_used: partial_header.blob_gas_used,
excess_blob_gas: partial_header.excess_blob_gas,
parent_beacon_block_root: partial_header.parent_beacon_block_root,
nonce: partial_header.nonce,
base_fee_per_gas: partial_header.base_fee,
},
Expand All @@ -94,6 +94,9 @@ pub struct PartialHeader {
pub timestamp: u64,
pub extra_data: Bytes,
pub mix_hash: B256,
pub blob_gas_used: Option<u128>,
pub excess_blob_gas: Option<u128>,
pub parent_beacon_block_root: Option<B256>,
pub nonce: B64,
pub base_fee: Option<u128>,
}
Expand All @@ -115,6 +118,9 @@ impl From<Header> for PartialHeader {
mix_hash: value.mix_hash,
nonce: value.nonce,
base_fee: value.base_fee_per_gas,
blob_gas_used: value.blob_gas_used,
excess_blob_gas: value.excess_blob_gas,
parent_beacon_block_root: value.parent_beacon_block_root,
}
}
}
Expand Down
45 changes: 35 additions & 10 deletions crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use revm::{
primitives::{CreateScheme, OptimismFields, TransactTo, TxEnv},
};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::ops::{Deref, Mul};

pub mod optimism;

Expand All @@ -45,12 +45,12 @@ pub fn transaction_request_to_typed(
max_fee_per_gas,
max_priority_fee_per_gas,
max_fee_per_blob_gas,
mut blob_versioned_hashes,
blob_versioned_hashes,
gas,
value,
input,
nonce,
mut access_list,
access_list,
sidecar,
transaction_type,
..
Expand All @@ -77,9 +77,9 @@ pub fn transaction_request_to_typed(
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
access_list.take(),
access_list.as_ref(),
max_fee_per_blob_gas,
blob_versioned_hashes.take(),
blob_versioned_hashes.as_ref(),
sidecar,
to,
) {
Expand Down Expand Up @@ -129,7 +129,7 @@ pub fn transaction_request_to_typed(
}))
}
// EIP4844
(Some(3), None, _, _, _, Some(_), Some(_), Some(sidecar), Some(to)) => {
(Some(3), None, _, _, _, Some(_), Some(_), Some(sidecar), to) => {
let tx = TxEip4844 {
nonce: nonce.unwrap_or_default(),
max_fee_per_gas: max_fee_per_gas.unwrap_or_default(),
Expand All @@ -138,7 +138,7 @@ pub fn transaction_request_to_typed(
gas_limit: gas.unwrap_or_default(),
value: value.unwrap_or(U256::ZERO),
input: input.into_input().unwrap_or_default(),
to: match to {
to: match to.unwrap_or(TxKind::Create) {
TxKind::Call(to) => to,
TxKind::Create => Address::ZERO,
},
Expand Down Expand Up @@ -619,9 +619,9 @@ pub enum TypedTransaction {
}

impl TypedTransaction {
/// Returns true if the transaction uses dynamic fees: EIP1559
/// Returns true if the transaction uses dynamic fees: EIP1559 or EIP4844
pub fn is_dynamic_fee(&self) -> bool {
matches!(self, TypedTransaction::EIP1559(_))
matches!(self, TypedTransaction::EIP1559(_)) || matches!(self, TypedTransaction::EIP4844(_))
}

pub fn gas_price(&self) -> u128 {
Expand Down Expand Up @@ -676,8 +676,33 @@ impl TypedTransaction {
}

/// Max cost of the transaction
/// It is the gas limit multiplied by the gas price,
/// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob
/// gas) is also added
pub fn max_cost(&self) -> u128 {
self.gas_limit().saturating_mul(self.gas_price())
let mut max_cost = self.gas_limit().saturating_mul(self.gas_price());

if self.is_eip4844() {
max_cost = max_cost.saturating_add(
self.blob_gas().unwrap_or(0).mul(self.max_fee_per_blob_gas().unwrap_or(0)),
)
}

max_cost
}

pub fn blob_gas(&self) -> Option<u128> {
match self {
TypedTransaction::EIP4844(tx) => Some(tx.tx().tx().blob_gas() as u128),
_ => None,
}
}

pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
TypedTransaction::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas),
_ => None,
}
}

/// Returns a helper type that contains commonly used values as fields
Expand Down
34 changes: 32 additions & 2 deletions crates/anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use foundry_evm::{
};
use parking_lot::RwLock;
use rand::thread_rng;
use revm::primitives::BlobExcessGasAndPrice;
use serde_json::{json, to_writer, Value};
use std::{
collections::HashMap,
Expand Down Expand Up @@ -98,6 +99,8 @@ pub struct NodeConfig {
pub gas_price: Option<u128>,
/// Default base fee
pub base_fee: Option<u128>,
/// Default blob excess gas and price
pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
/// The hardfork to use
pub hardfork: Option<Hardfork>,
/// Signer accounts that will be initialised with `genesis_balance` in the genesis block
Expand Down Expand Up @@ -398,6 +401,7 @@ impl Default for NodeConfig {
fork_block_number: None,
account_generator: None,
base_fee: None,
blob_excess_gas_and_price: None,
enable_tracing: true,
enable_steps_tracing: false,
enable_auto_impersonate: false,
Expand Down Expand Up @@ -447,6 +451,17 @@ impl NodeConfig {
self.gas_price.unwrap_or(INITIAL_GAS_PRICE)
}

pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
if let Some(blob_excess_gas_and_price) = &self.blob_excess_gas_and_price {
blob_excess_gas_and_price.clone()
} else if let Some(excess_blob_gas) = self.genesis.as_ref().and_then(|g| g.excess_blob_gas)
{
BlobExcessGasAndPrice::new(excess_blob_gas as u64)
} else {
BlobExcessGasAndPrice { blob_gasprice: 0, excess_blob_gas: 0 }
}
}

/// Returns the base fee to use
pub fn get_hardfork(&self) -> Hardfork {
self.hardfork.unwrap_or_default()
Expand Down Expand Up @@ -876,8 +891,12 @@ impl NodeConfig {
};
let mut env = EnvWithHandlerCfg::new(Box::new(env), cfg.handler_cfg);

let fees =
FeeManager::new(cfg.handler_cfg.spec_id, self.get_base_fee(), self.get_gas_price());
let fees = FeeManager::new(
cfg.handler_cfg.spec_id,
self.get_base_fee(),
self.get_gas_price(),
self.get_blob_excess_gas_and_price(),
);

let (db, fork): (Arc<tokio::sync::RwLock<Box<dyn Db>>>, Option<ClientFork>) =
if let Some(eth_rpc_url) = self.eth_rpc_url.clone() {
Expand Down Expand Up @@ -1075,6 +1094,17 @@ latest block number: {latest_block}"
// update next base fee
fees.set_base_fee(next_block_base_fee);
}
if let (Some(blob_excess_gas), Some(blob_gas_used)) =
(block.header.excess_blob_gas, block.header.blob_gas_used)
{
env.block.blob_excess_gas_and_price =
Some(BlobExcessGasAndPrice::new(blob_excess_gas as u64));
let next_block_blob_excess_gas =
fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used);
fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
next_block_blob_excess_gas,
));
}
}

// use remote gas price
Expand Down
47 changes: 45 additions & 2 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ use crate::{
},
filter::{EthFilter, Filters, LogsFilter},
mem::transaction_build,
revm::primitives::Output,
revm::primitives::{BlobExcessGasAndPrice, Output},
ClientFork, LoggingManager, Miner, MiningMode, StorageInfo,
};
use alloy_consensus::TxEip4844Variant;
use alloy_dyn_abi::TypedData;
use alloy_eips::eip2718::Encodable2718;
use alloy_network::eip2718::Decodable2718;
Expand Down Expand Up @@ -550,6 +551,11 @@ impl EthApi {
Ok(U256::from(self.backend.gas_price()))
}

/// Returns the excess blob gas and current blob gas price
pub fn excess_blob_gas_and_price(&self) -> Result<Option<BlobExcessGasAndPrice>> {
Ok(self.backend.excess_blob_gas_and_price())
}

/// Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or
/// 'tip', to get a transaction included in the current block.
///
Expand Down Expand Up @@ -977,6 +983,7 @@ impl EthApi {
request.gas_price,
request.max_fee_per_gas,
request.max_priority_fee_per_gas,
request.max_fee_per_blob_gas,
)?
.or_zero_fees();
// this can be blocking for a bit, especially in forking mode
Expand Down Expand Up @@ -1292,6 +1299,8 @@ impl EthApi {
// <https://eips.ethereum.org/EIPS/eip-1559>
if let Some(block) = fee_history.get(&n) {
response.base_fee_per_gas.push(block.base_fee);
response.base_fee_per_blob_gas.push(block.base_fee_per_blob_gas.unwrap_or(0));
response.blob_gas_used_ratio.push(block.blob_gas_used_ratio);
response.gas_used_ratio.push(block.gas_used_ratio);

// requested percentiles
Expand All @@ -1318,6 +1327,11 @@ impl EthApi {
// newest block"
response.base_fee_per_gas.push(self.backend.fees().base_fee());

// Same goes for the `base_fee_per_blob_gas`:
// > [..] includes the next block after the newest of the returned range, because this
// > value can be derived from the newest block.
response.base_fee_per_blob_gas.push(self.backend.fees().base_fee_per_blob_gas());

Ok(response)
}

Expand Down Expand Up @@ -1424,6 +1438,7 @@ impl EthApi {
request.gas_price,
request.max_fee_per_gas,
request.max_priority_fee_per_gas,
request.max_fee_per_blob_gas,
)?
.or_zero_fees();

Expand Down Expand Up @@ -2184,6 +2199,7 @@ impl EthApi {
request.gas_price,
request.max_fee_per_gas,
request.max_priority_fee_per_gas,
request.max_fee_per_blob_gas,
)?
.or_zero_fees();

Expand Down Expand Up @@ -2387,6 +2403,7 @@ impl EthApi {
) -> Result<TypedTransactionRequest> {
let chain_id = request.chain_id.unwrap_or_else(|| self.chain_id());
let max_fee_per_gas = request.max_fee_per_gas;
let max_fee_per_blob_gas = request.max_fee_per_blob_gas;
let gas_price = request.gas_price;

let gas_limit = request.gas.unwrap_or(self.backend.gas_limit());
Expand Down Expand Up @@ -2419,11 +2436,37 @@ impl EthApi {
}
TypedTransactionRequest::EIP1559(m)
}
Some(TypedTransactionRequest::EIP4844(m)) => {
TypedTransactionRequest::EIP4844(match m {
// We only accept the TxEip4844 variant which has the sidecar.
TxEip4844Variant::TxEip4844WithSidecar(mut m) => {
m.tx.nonce = nonce;
m.tx.chain_id = chain_id;
m.tx.gas_limit = gas_limit;
if max_fee_per_gas.is_none() {
m.tx.max_fee_per_gas =
self.gas_price().unwrap_or_default().to::<u128>();
}
if max_fee_per_blob_gas.is_none() {
m.tx.max_fee_per_blob_gas = self
.excess_blob_gas_and_price()
.unwrap_or_default()
.map_or(0, |g| g.blob_gasprice)
}
TxEip4844Variant::TxEip4844WithSidecar(m)
}
// It is not valid to receive a TxEip4844 without a sidecar, therefore
// we must reject it.
TxEip4844Variant::TxEip4844(_) => {
return Err(BlockchainError::FailedToDecodeTransaction)
}
})
}
Some(TypedTransactionRequest::Deposit(mut m)) => {
m.gas_limit = gas_limit;
TypedTransactionRequest::Deposit(m)
}
_ => return Err(BlockchainError::FailedToDecodeTransaction),
None => return Err(BlockchainError::FailedToDecodeTransaction),
};
Ok(request)
}
Expand Down
Loading

0 comments on commit 7a676f8

Please sign in to comment.