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

chore: move fill_block_env to ConfigureEvmEnv #9238

Merged
merged 4 commits into from
Jul 2, 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
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 {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was not quite sure where to move this, but revm was not the place anyway

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine for now

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