From d833f1aed9f54cfee27ab8d6f8d342357ee0260f Mon Sep 17 00:00:00 2001 From: Rupam Dey <117000803+rupam-04@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:45:07 +0530 Subject: [PATCH] feat: add new crate op-beacon-core (#7848) Co-authored-by: Matthias Seitz --- Cargo.lock | 9 ++ Cargo.toml | 4 +- crates/consensus/common/src/validation.rs | 17 +++- crates/optimism/consensus/Cargo.toml | 23 +++++ crates/optimism/consensus/src/lib.rs | 103 ++++++++++++++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 crates/optimism/consensus/Cargo.toml create mode 100644 crates/optimism/consensus/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 863fada9c..5262d2c41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7113,6 +7113,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-optimism-consensus" +version = "0.2.0-beta.6" +dependencies = [ + "reth-consensus", + "reth-consensus-common", + "reth-primitives", +] + [[package]] name = "reth-optimism-payload-builder" version = "0.2.0-beta.6" diff --git a/Cargo.toml b/Cargo.toml index f3ac31674..817f82993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "crates/ethereum/engine-primitives/", "crates/node-ethereum/", "crates/node-builder/", + "crates/optimism/consensus", "crates/optimism/node/", "crates/optimism/evm/", "crates/node-core/", @@ -222,6 +223,7 @@ reth-engine-primitives = { path = "crates/engine-primitives" } reth-ethereum-engine-primitives = { path = "crates/ethereum/engine-primitives" } reth-node-builder = { path = "crates/node-builder" } reth-node-ethereum = { path = "crates/node-ethereum" } +reth-node-events = { path = "crates/node/events" } reth-node-optimism = { path = "crates/optimism/node" } reth-evm-optimism = { path = "crates/optimism/evm" } reth-node-core = { path = "crates/node-core" } @@ -270,7 +272,7 @@ reth-tracing = { path = "crates/tracing" } reth-transaction-pool = { path = "crates/transaction-pool" } reth-trie = { path = "crates/trie" } reth-trie-parallel = { path = "crates/trie-parallel" } -reth-node-events = { path = "crates/node/events" } +reth-optimism-consensus = { path = "crates/optimism/consensus" } # revm revm = { version = "8.0.0", features = ["std", "secp256k1"], default-features = false } diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 3ed01f637..06b2303a8 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -3,7 +3,10 @@ use reth_consensus::ConsensusError; use reth_interfaces::RethResult; use reth_primitives::{ - constants::eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}, + constants::{ + eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}, + MAXIMUM_EXTRA_DATA_SIZE, + }, BlockNumber, ChainSpec, GotExpected, Hardfork, Header, InvalidTransactionError, SealedBlock, SealedHeader, Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, @@ -321,6 +324,18 @@ pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), Cons Ok(()) } +/// Validates the header's extradata according to the beacon consensus rules. +/// +/// From yellow paper: extraData: An arbitrary byte array containing data relevant to this block. +/// This must be 32 bytes or fewer; formally Hx. +pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> { + if header.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE { + Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data.len() }) + } else { + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/optimism/consensus/Cargo.toml b/crates/optimism/consensus/Cargo.toml new file mode 100644 index 000000000..4ebbaa8d8 --- /dev/null +++ b/crates/optimism/consensus/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "reth-optimism-consensus" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-consensus-common.workspace = true +reth-primitives.workspace = true +reth-consensus.workspace = true + +[features] +optimism = [ + "reth-primitives/optimism", +] \ No newline at end of file diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs new file mode 100644 index 000000000..9a905adfa --- /dev/null +++ b/crates/optimism/consensus/src/lib.rs @@ -0,0 +1,103 @@ +//! Optimism Consensus implementation. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] + +use reth_consensus::{Consensus, ConsensusError}; +use reth_consensus_common::{validation, validation::validate_header_extradata}; +use reth_primitives::{ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256}; +use std::{sync::Arc, time::SystemTime}; + +/// Optimism consensus implementation. +/// +/// Provides basic checks as outlined in the execution specs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OptimismBeaconConsensus { + /// Configuration + chain_spec: Arc, +} + +impl OptimismBeaconConsensus { + /// Create a new instance of [OptimismBeaconConsensus] + /// + /// # Panics + /// + /// If given chain spec is not optimism [ChainSpec::is_optimism] + pub fn new(chain_spec: Arc) -> Self { + assert!(chain_spec.is_optimism(), "optimism consensus only valid for optimism chains"); + Self { chain_spec } + } +} + +impl Consensus for OptimismBeaconConsensus { + fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { + validation::validate_header_standalone(header, &self.chain_spec)?; + Ok(()) + } + + fn validate_header_against_parent( + &self, + header: &SealedHeader, + parent: &SealedHeader, + ) -> Result<(), ConsensusError> { + header.validate_against_parent(parent, &self.chain_spec).map_err(ConsensusError::from)?; + Ok(()) + } + + fn validate_header_with_total_difficulty( + &self, + header: &Header, + _total_difficulty: U256, + ) -> Result<(), ConsensusError> { + // with OP-stack Bedrock activation number determines when TTD (eth Merge) has been reached. + let is_post_merge = self.chain_spec.is_bedrock_active_at_block(header.number); + + if is_post_merge { + if header.nonce != 0 { + return Err(ConsensusError::TheMergeNonceIsNotZero) + } + + if header.ommers_hash != EMPTY_OMMER_ROOT_HASH { + return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty) + } + + // Post-merge, the consensus layer is expected to perform checks such that the block + // timestamp is a function of the slot. This is different from pre-merge, where blocks + // are only allowed to be in the future (compared to the system's clock) by a certain + // threshold. + // + // Block validation with respect to the parent should ensure that the block timestamp + // is greater than its parent timestamp. + + // validate header extradata for all networks post merge + validate_header_extradata(header)?; + + // mixHash is used instead of difficulty inside EVM + // https://eips.ethereum.org/EIPS/eip-4399#using-mixhash-field-instead-of-difficulty + } else { + // Check if timestamp is in the future. Clock can drift but this can be consensus issue. + let present_timestamp = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + + if header.exceeds_allowed_future_timestamp(present_timestamp) { + return Err(ConsensusError::TimestampIsInFuture { + timestamp: header.timestamp, + present_timestamp, + }) + } + } + + Ok(()) + } + + fn validate_block(&self, block: &SealedBlock) -> Result<(), ConsensusError> { + validation::validate_block_standalone(block, &self.chain_spec) + } +}