Skip to content

Commit

Permalink
chore: move fill_block_env to ConfigureEvmEnv (#9238)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshieDo authored Jul 2, 2024
1 parent 6eca557 commit 82770b0
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 123 deletions.
50 changes: 46 additions & 4 deletions crates/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use core::ops::Deref;

use reth_chainspec::ChainSpec;
use reth_primitives::{
revm::env::fill_block_env, Address, Header, TransactionSigned, TransactionSignedEcRecovered,
U256,
header::block_coinbase, Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256,
};
use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector};
use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv};
Expand Down Expand Up @@ -126,8 +125,46 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
total_difficulty: U256,
);

/// Fill [`BlockEnv`] field according to the chain spec and given header
fn fill_block_env(
&self,
block_env: &mut BlockEnv,
chain_spec: &ChainSpec,
header: &Header,
after_merge: bool,
) {
let coinbase = block_coinbase(chain_spec, header, after_merge);
Self::fill_block_env_with_coinbase(block_env, header, after_merge, coinbase);
}

/// Fill block environment with coinbase.
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);
}
}

/// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and
/// [`fill_block_env`].
/// [`ConfigureEvmEnv::fill_block_env`].
fn fill_cfg_and_block_env(
cfg: &mut CfgEnvWithHandlerCfg,
block_env: &mut BlockEnv,
Expand All @@ -137,6 +174,11 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
) {
Self::fill_cfg_env(cfg, chain_spec, header, total_difficulty);
let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE;
fill_block_env(block_env, chain_spec, header, after_merge);
Self::fill_block_env_with_coinbase(
block_env,
header,
after_merge,
block_coinbase(chain_spec, header, after_merge),
);
}
}
69 changes: 69 additions & 0 deletions crates/primitives/src/header.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,71 @@
//! Header types.

use crate::{recover_signer_unchecked, Address, Bytes};
use alloy_rlp::{Decodable, Encodable};
use bytes::BufMut;
use reth_chainspec::{Chain, ChainSpec};
use reth_codecs::derive_arbitrary;
use serde::{Deserialize, Serialize};

pub use reth_primitives_traits::{Header, HeaderError, SealedHeader};

/// Return the coinbase address for the given header and chain spec.
pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool) -> Address {
// Clique consensus fills the EXTRA_SEAL (last 65 bytes) of the extra data with the
// signer's signature.
//
// On the genesis block, the extra data is filled with zeros, so we should not attempt to
// recover the signer on the genesis block.
//
// From EIP-225:
//
// * `EXTRA_SEAL`: Fixed number of extra-data suffix bytes reserved for signer seal.
// * 65 bytes fixed as signatures are based on the standard `secp256k1` curve.
// * Filled with zeros on genesis block.
if chain_spec.chain == Chain::goerli() && !after_merge && header.number > 0 {
recover_header_signer(header).unwrap_or_else(|err| {
panic!(
"Failed to recover goerli Clique Consensus signer from header ({}, {}) using extradata {}: {:?}",
header.number, header.hash_slow(), header.extra_data, err
)
})
} else {
header.beneficiary
}
}

/// Error type for recovering Clique signer from a header.
#[derive(Debug, thiserror_no_std::Error)]
pub enum CliqueSignerRecoveryError {
/// Header extradata is too short.
#[error("Invalid extra data length")]
InvalidExtraData,
/// Recovery failed.
#[error("Invalid signature: {0}")]
InvalidSignature(#[from] secp256k1::Error),
}

/// Recover the account from signed header per clique consensus rules.
pub fn recover_header_signer(header: &Header) -> Result<Address, CliqueSignerRecoveryError> {
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()
.map_err(|_| CliqueSignerRecoveryError::InvalidExtraData)?;
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()
};

// TODO: this is currently unchecked recovery, does this need to be checked w.r.t EIP-2?
recover_signer_unchecked(&signature, &seal_hash.0)
.map_err(CliqueSignerRecoveryError::InvalidSignature)
}

/// Represents the direction for a headers request depending on the `reverse` field of the request.
/// > The response must contain a number of block headers, of rising number when reverse is 0,
/// > falling when 1
Expand Down Expand Up @@ -87,6 +146,7 @@ impl From<HeadersDirection> for bool {

#[cfg(test)]
mod tests {
use super::*;
use crate::{
address, b256, bloom, bytes, hex, Address, Bytes, Header, HeadersDirection, B256, U256,
};
Expand Down Expand Up @@ -353,4 +413,13 @@ mod tests {
Header::decode(&mut data.as_slice())
.expect_err("blob_gas_used size should make this header decoding fail");
}

#[test]
fn test_recover_genesis_goerli_signer() {
// just ensures that `block_coinbase` does not panic on the genesis block
let chain_spec = reth_chainspec::GOERLI.clone();
let header = chain_spec.genesis_header();
let block_coinbase = block_coinbase(&chain_spec, &header, false);
assert_eq!(block_coinbase, header.beneficiary);
}
}
116 changes: 2 additions & 114 deletions crates/primitives/src/revm/env.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use crate::{
recover_signer_unchecked,
revm_primitives::{BlockEnv, Env, TxEnv},
Address, Bytes, Header, TxKind, B256, U256,
revm_primitives::{Env, TxEnv},
Address, Bytes, TxKind, B256, U256,
};
use reth_chainspec::{Chain, ChainSpec};

use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS};
#[cfg(feature = "optimism")]
Expand All @@ -12,101 +10,6 @@ use revm_primitives::OptimismFields;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

/// 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 {
// Clique consensus fills the EXTRA_SEAL (last 65 bytes) of the extra data with the
// signer's signature.
//
// On the genesis block, the extra data is filled with zeros, so we should not attempt to
// recover the signer on the genesis block.
//
// From EIP-225:
//
// * `EXTRA_SEAL`: Fixed number of extra-data suffix bytes reserved for signer seal.
// * 65 bytes fixed as signatures are based on the standard `secp256k1` curve.
// * Filled with zeros on genesis block.
if chain_spec.chain == Chain::goerli() && !after_merge && header.number > 0 {
recover_header_signer(header).unwrap_or_else(|err| {
panic!(
"Failed to recover goerli Clique Consensus signer from header ({}, {}) using extradata {}: {:?}",
header.number, header.hash_slow(), header.extra_data, err
)
})
} else {
header.beneficiary
}
}

/// Error type for recovering Clique signer from a header.
#[derive(Debug, thiserror_no_std::Error)]
pub enum CliqueSignerRecoveryError {
/// Header extradata is too short.
#[error("Invalid extra data length")]
InvalidExtraData,
/// Recovery failed.
#[error("Invalid signature: {0}")]
InvalidSignature(#[from] secp256k1::Error),
}

/// Recover the account from signed header per clique consensus rules.
pub fn recover_header_signer(header: &Header) -> Result<Address, CliqueSignerRecoveryError> {
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()
.map_err(|_| CliqueSignerRecoveryError::InvalidExtraData)?;
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()
};

// TODO: this is currently unchecked recovery, does this need to be checked w.r.t EIP-2?
recover_signer_unchecked(&signature, &seal_hash.0)
.map_err(CliqueSignerRecoveryError::InvalidSignature)
}

/// Fill transaction environment with the EIP-4788 system contract message data.
///
/// This requirements for the beacon root contract call defined by
Expand Down Expand Up @@ -195,18 +98,3 @@ fn fill_tx_env_with_system_contract_call(
// disable the base fee check for this call by setting the base fee to zero
env.block.basefee = U256::ZERO;
}

#[cfg(test)]
mod tests {
use super::*;
use reth_chainspec::GOERLI;

#[test]
fn test_recover_genesis_goerli_signer() {
// just ensures that `block_coinbase` does not panic on the genesis block
let chain_spec = GOERLI.clone();
let header = chain_spec.genesis_header();
let block_coinbase = block_coinbase(&chain_spec, &header, false);
assert_eq!(block_coinbase, header.beneficiary);
}
}
15 changes: 10 additions & 5 deletions crates/rpc/rpc-eth-api/src/helpers/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
//! RPC methods.

use futures::Future;
use reth_primitives::{
revm::env::fill_block_env_with_coinbase, Address, BlockId, BlockNumberOrTag, Bytes, Header,
B256, U256,
use reth_evm::ConfigureEvmEnv;
use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, Header, B256, U256};
use reth_provider::{
BlockIdReader, ChainSpecProvider, StateProvider, StateProviderBox, StateProviderFactory,
};
use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory};
use reth_rpc_eth_types::{
EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError,
};
Expand Down Expand Up @@ -209,7 +209,12 @@ pub trait LoadState {
let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?;

let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE;
fill_block_env_with_coinbase(&mut block_env, header, after_merge, header.beneficiary);
self.evm_config().fill_block_env(
&mut block_env,
&LoadPendingBlock::provider(self).chain_spec(),
header,
after_merge,
);

Ok((cfg, block_env))
}
Expand Down

0 comments on commit 82770b0

Please sign in to comment.