feat(primitives): move revm-primitives to reth-primitives (#5088)

This commit is contained in:
Thomas Coratger
2023-10-20 00:35:46 +02:00
committed by GitHub
parent 5dd5555c5c
commit 5d217a2867
32 changed files with 90 additions and 109 deletions

View File

@ -65,6 +65,8 @@ proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }
strum = { workspace = true, features = ["derive"] }
revm.workspace = true
[dev-dependencies]
serde_json.workspace = true
test-fuzz = "4"
@ -83,8 +85,8 @@ hash-db = "~0.15"
reth-primitives = { path = ".", features = ["value-256"] }
# necessary so we don't hit a "undeclared 'std'":
# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
# necessary so we don't hit a "undeclared 'std'":
# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
secp256k1.workspace = true
criterion = "0.5"
pprof = { version = "0.12", features = ["flamegraph", "frame-pointer", "criterion"] }

View File

@ -39,6 +39,8 @@ mod precaution;
pub mod proofs;
mod prune;
mod receipt;
/// Helpers for working with revm
pub mod revm;
pub mod serde_helper;
pub mod snapshot;
pub mod stage;

View File

@ -0,0 +1,61 @@
use crate::{
revm_primitives::{AccountInfo, Log},
Account, Address, Log as RethLog, TransactionKind, KECCAK_EMPTY, U256,
};
use revm::{
interpreter::gas::initial_tx_gas,
primitives::{MergeSpec, ShanghaiSpec},
};
/// Check equality between Revm and Reth `Log`s.
pub fn is_log_equal(revm_log: &Log, reth_log: &crate::Log) -> bool {
revm_log.address == reth_log.address &&
revm_log.data == reth_log.data &&
revm_log.topics == reth_log.topics
}
/// Converts a Revm `Log` into a Reth `Log`.
pub fn into_reth_log(log: Log) -> RethLog {
RethLog { address: log.address, topics: log.topics, data: log.data }
}
/// Converts a Revm [`AccountInfo`] into a Reth [`Account`].
///
/// Sets `bytecode_hash` to `None` if `code_hash` is [`KECCAK_EMPTY`].
pub fn into_reth_acc(revm_acc: AccountInfo) -> Account {
let code_hash = revm_acc.code_hash;
Account {
balance: revm_acc.balance,
nonce: revm_acc.nonce,
bytecode_hash: (code_hash != KECCAK_EMPTY).then_some(code_hash),
}
}
/// Converts a Revm [`AccountInfo`] into a Reth [`Account`].
///
/// Sets `code_hash` to [`KECCAK_EMPTY`] if `bytecode_hash` is `None`.
pub fn into_revm_acc(reth_acc: Account) -> AccountInfo {
AccountInfo {
balance: reth_acc.balance,
nonce: reth_acc.nonce,
code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY),
code: None,
}
}
/// Calculates the Intrinsic Gas usage for a Transaction
///
/// Caution: This only checks past the Merge hardfork.
#[inline]
pub fn calculate_intrinsic_gas_after_merge(
input: &[u8],
kind: &TransactionKind,
access_list: &[(Address, Vec<U256>)],
is_shanghai: bool,
) -> u64 {
if is_shanghai {
initial_tx_gas::<ShanghaiSpec>(input, kind.is_create(), access_list)
} else {
initial_tx_gas::<MergeSpec>(input, kind.is_create(), access_list)
}
}

View File

@ -0,0 +1,180 @@
use crate::{revm_primitives, ChainSpec, Hardfork, Head};
/// Returns the spec id at the given timestamp.
///
/// Note: This is only intended to be used after the merge, when hardforks are activated by
/// timestamp.
pub fn revm_spec_by_timestamp_after_merge(
chain_spec: &ChainSpec,
timestamp: u64,
) -> revm_primitives::SpecId {
if chain_spec.is_cancun_active_at_timestamp(timestamp) {
revm_primitives::CANCUN
} else if chain_spec.is_shanghai_active_at_timestamp(timestamp) {
revm_primitives::SHANGHAI
} else {
revm_primitives::MERGE
}
}
/// return revm_spec from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId {
if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
revm_primitives::CANCUN
} else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
revm_primitives::SHANGHAI
} else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) {
revm_primitives::MERGE
} else if chain_spec.fork(Hardfork::London).active_at_head(&block) {
revm_primitives::LONDON
} else if chain_spec.fork(Hardfork::Berlin).active_at_head(&block) {
revm_primitives::BERLIN
} else if chain_spec.fork(Hardfork::Istanbul).active_at_head(&block) {
revm_primitives::ISTANBUL
} else if chain_spec.fork(Hardfork::Petersburg).active_at_head(&block) {
revm_primitives::PETERSBURG
} else if chain_spec.fork(Hardfork::Byzantium).active_at_head(&block) {
revm_primitives::BYZANTIUM
} else if chain_spec.fork(Hardfork::SpuriousDragon).active_at_head(&block) {
revm_primitives::SPURIOUS_DRAGON
} else if chain_spec.fork(Hardfork::Tangerine).active_at_head(&block) {
revm_primitives::TANGERINE
} else if chain_spec.fork(Hardfork::Homestead).active_at_head(&block) {
revm_primitives::HOMESTEAD
} else if chain_spec.fork(Hardfork::Frontier).active_at_head(&block) {
revm_primitives::FRONTIER
} else {
panic!(
"invalid hardfork chainspec: expected at least one hardfork, got {:?}",
chain_spec.hardforks
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ChainSpecBuilder, Head, MAINNET, U256};
#[test]
fn test_to_revm_spec() {
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().cancun_activated().build(), Head::default()),
revm_primitives::CANCUN
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().shanghai_activated().build(), Head::default()),
revm_primitives::SHANGHAI
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), Head::default()),
revm_primitives::MERGE
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), Head::default()),
revm_primitives::LONDON
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), Head::default()),
revm_primitives::BERLIN
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().istanbul_activated().build(), Head::default()),
revm_primitives::ISTANBUL
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().petersburg_activated().build(), Head::default()),
revm_primitives::PETERSBURG
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().byzantium_activated().build(), Head::default()),
revm_primitives::BYZANTIUM
);
assert_eq!(
revm_spec(
&ChainSpecBuilder::mainnet().spurious_dragon_activated().build(),
Head::default()
),
revm_primitives::SPURIOUS_DRAGON
);
assert_eq!(
revm_spec(
&ChainSpecBuilder::mainnet().tangerine_whistle_activated().build(),
Head::default()
),
revm_primitives::TANGERINE
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().homestead_activated().build(), Head::default()),
revm_primitives::HOMESTEAD
);
assert_eq!(
revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), Head::default()),
revm_primitives::FRONTIER
);
}
#[test]
fn test_eth_spec() {
assert_eq!(
revm_spec(
&MAINNET,
Head {
total_difficulty: U256::from(58_750_000_000_000_000_000_010_u128),
difficulty: U256::from(10_u128),
..Default::default()
}
),
revm_primitives::MERGE
);
// TTD trumps the block number
assert_eq!(
revm_spec(
&MAINNET,
Head {
number: 15537394 - 10,
total_difficulty: U256::from(58_750_000_000_000_000_000_010_u128),
difficulty: U256::from(10_u128),
..Default::default()
}
),
revm_primitives::MERGE
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 15537394 - 10, ..Default::default() }),
revm_primitives::LONDON
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 12244000 + 10, ..Default::default() }),
revm_primitives::BERLIN
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 12244000 - 10, ..Default::default() }),
revm_primitives::ISTANBUL
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 7280000 + 10, ..Default::default() }),
revm_primitives::PETERSBURG
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 7280000 - 10, ..Default::default() }),
revm_primitives::BYZANTIUM
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 2675000 + 10, ..Default::default() }),
revm_primitives::SPURIOUS_DRAGON
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 2675000 - 10, ..Default::default() }),
revm_primitives::TANGERINE
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 1150000 + 10, ..Default::default() }),
revm_primitives::HOMESTEAD
);
assert_eq!(
revm_spec(&MAINNET, Head { number: 1150000 - 10, ..Default::default() }),
revm_primitives::FRONTIER
);
}
}

View File

@ -0,0 +1,295 @@
use crate::{
constants::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS},
recover_signer,
revm::config::revm_spec,
revm_primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv},
Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, B256, U256,
};
/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
pub fn fill_cfg_and_block_env(
cfg: &mut CfgEnv,
block_env: &mut BlockEnv,
chain_spec: &ChainSpec,
header: &Header,
total_difficulty: U256,
) {
fill_cfg_env(cfg, chain_spec, header, total_difficulty);
let after_merge = cfg.spec_id >= SpecId::MERGE;
fill_block_env(block_env, chain_spec, header, after_merge);
}
/// Fill [CfgEnv] fields according to the chain spec and given header
pub fn fill_cfg_env(
cfg_env: &mut CfgEnv,
chain_spec: &ChainSpec,
header: &Header,
total_difficulty: U256,
) {
let spec_id = revm_spec(
chain_spec,
Head {
number: header.number,
timestamp: header.timestamp,
difficulty: header.difficulty,
total_difficulty,
hash: Default::default(),
},
);
cfg_env.chain_id = chain_spec.chain().id();
cfg_env.spec_id = spec_id;
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
}
/// Fill block environment from Block.
pub fn fill_block_env(
block_env: &mut BlockEnv,
chain_spec: &ChainSpec,
header: &Header,
after_merge: bool,
) {
let coinbase = block_coinbase(chain_spec, header, after_merge);
fill_block_env_with_coinbase(block_env, header, after_merge, coinbase);
}
/// Fill block environment with coinbase.
#[inline]
pub fn fill_block_env_with_coinbase(
block_env: &mut BlockEnv,
header: &Header,
after_merge: bool,
coinbase: Address,
) {
block_env.number = U256::from(header.number);
block_env.coinbase = coinbase;
block_env.timestamp = U256::from(header.timestamp);
if after_merge {
block_env.prevrandao = Some(header.mix_hash);
block_env.difficulty = U256::ZERO;
} else {
block_env.difficulty = header.difficulty;
block_env.prevrandao = None;
}
block_env.basefee = U256::from(header.base_fee_per_gas.unwrap_or_default());
block_env.gas_limit = U256::from(header.gas_limit);
// EIP-4844 excess blob gas of this block, introduced in Cancun
if let Some(excess_blob_gas) = header.excess_blob_gas {
block_env.set_blob_excess_gas_and_price(excess_blob_gas);
}
}
/// Return the coinbase address for the given header and chain spec.
pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool) -> Address {
if chain_spec.chain == Chain::goerli() && !after_merge {
recover_header_signer(header).expect("failed to recover signer")
} else {
header.beneficiary
}
}
/// Recover the account from signed header per clique consensus rules.
pub fn recover_header_signer(header: &Header) -> Option<Address> {
let extra_data_len = header.extra_data.len();
// Fixed number of extra-data suffix bytes reserved for signer signature.
// 65 bytes fixed as signatures are based on the standard secp256k1 curve.
// Filled with zeros on genesis block.
let signature_start_byte = extra_data_len - 65;
let signature: [u8; 65] = header.extra_data[signature_start_byte..].try_into().ok()?;
let seal_hash = {
let mut header_to_seal = header.clone();
header_to_seal.extra_data = Bytes::from(header.extra_data[..signature_start_byte].to_vec());
header_to_seal.hash_slow()
};
recover_signer(&signature, &seal_hash.0).ok()
}
/// Returns a new [TxEnv] filled with the transaction's data.
pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEnv {
let mut tx_env = TxEnv::default();
fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer());
tx_env
}
/// Fill transaction environment with the EIP-4788 system contract message data.
///
/// This requirements for the beacon root contract call defined by
/// [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) are:
///
/// At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e.
/// before processing any transactions), call `BEACON_ROOTS_ADDRESS` as `SYSTEM_ADDRESS` with the
/// 32-byte input of `header.parent_beacon_block_root`, a gas limit of `30_000_000`, and `0` value.
/// This will trigger the `set()` routine of the beacon roots contract. This is a system operation
/// and therefore:
/// * the call must execute to completion
/// * the call does not count against the blocks gas limit
/// * the call does not follow the EIP-1559 burn semantics - no value should be transferred as
/// part of the call
/// * if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently
pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_block_root: B256) {
env.tx = TxEnv {
caller: SYSTEM_ADDRESS,
transact_to: TransactTo::Call(BEACON_ROOTS_ADDRESS),
// Explicitly set nonce to None so revm does not do any nonce checks
nonce: None,
gas_limit: 30_000_000,
value: U256::ZERO,
data: parent_beacon_block_root.0.to_vec().into(),
// Setting the gas price to zero enforces that no value is transferred as part of the call,
// and that the call will not count against the block's gas limit
gas_price: U256::ZERO,
// The chain ID check is not relevant here and is disabled if set to None
chain_id: None,
// Setting the gas priority fee to None ensures the effective gas price is derived from the
// `gas_price` field, which we need to be zero
gas_priority_fee: None,
access_list: Vec::new(),
// blob fields can be None for this tx
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
};
// ensure the block gas limit is >= the tx
env.block.gas_limit = U256::from(env.tx.gas_limit);
// disable the base fee check for this call by setting the base fee to zero
env.block.basefee = U256::ZERO;
}
/// Fill transaction environment from [TransactionSignedEcRecovered].
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer())
}
/// Fill transaction environment from a [Transaction] and the given sender address.
pub fn fill_tx_env<T>(tx_env: &mut TxEnv, transaction: T, sender: Address)
where
T: AsRef<Transaction>,
{
tx_env.caller = sender;
match transaction.as_ref() {
Transaction::Legacy(TxLegacy {
nonce,
chain_id,
gas_price,
gas_limit,
to,
value,
input,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*gas_price);
tx_env.gas_priority_fee = None;
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = *chain_id;
tx_env.nonce = Some(*nonce);
tx_env.access_list.clear();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
}
Transaction::Eip2930(TxEip2930 {
nonce,
chain_id,
gas_price,
gas_limit,
to,
value,
input,
access_list,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*gas_price);
tx_env.gas_priority_fee = None;
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
.0
.iter()
.map(|l| {
(l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect())
})
.collect();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
}
Transaction::Eip1559(TxEip1559 {
nonce,
chain_id,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
input,
access_list,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas));
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
.0
.iter()
.map(|l| {
(l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect())
})
.collect();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
}
Transaction::Eip4844(TxEip4844 {
nonce,
chain_id,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
access_list,
blob_versioned_hashes,
max_fee_per_blob_gas,
input,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas));
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
.0
.iter()
.map(|l| {
(l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect())
})
.collect();
tx_env.blob_hashes = blob_versioned_hashes.clone();
tx_env.max_fee_per_blob_gas = Some(U256::from(*max_fee_per_blob_gas));
}
}
}

View File

@ -0,0 +1,20 @@
/// The `compat` module contains a set of utility functions that bridge the gap between Revm and
/// Reth Ethereum implementations.
///
/// These functions enable the conversion of data structures between the two implementations, such
/// as converting `Log` structures, `AccountInfo`, and `Account` objects.
///
/// Additionally, it provides a function to calculate intrinsic gas usage for transactions beyond
/// the Merge hardfork, offering compatibility for both Shanghai and Merge Ethereum specifications.
///
/// These utilities facilitate interoperability and data exchange between Revm and Reth
/// implementations.
pub mod compat;
/// Reth block execution/validation configuration and constants
pub mod config;
/// The `env` module provides essential utilities for managing Ethereum transaction and block
/// environments.
///
/// It includes functions to fill transaction and block environments with relevant data, handle
/// system contract calls, and recover the signer of Ethereum headers.
pub mod env;