diff --git a/Cargo.lock b/Cargo.lock index 29cabf6..08248a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,6 +399,7 @@ dependencies = [ "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-engine 0.7.2", "alloy-rpc-types-eth 0.7.2", "alloy-transport", "alloy-transport-http", @@ -822,7 +823,7 @@ dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http 1.1.0", + "http 1.2.0", "rustls", "serde_json", "tokio", @@ -833,9 +834,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b2e366c0debf0af77766c23694a3f863b02633050e71e096e257ffbd395e50" +checksum = "650743e32cef16455a3f67197d9c2a7d00b09ac663d625f4da2fe912b098ddf3" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -913,9 +914,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "aquamarine" @@ -1644,9 +1645,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" dependencies = [ "clap_builder", "clap_derive", @@ -1654,9 +1655,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" dependencies = [ "anstream", "anstyle", @@ -2799,7 +2800,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http 1.2.0", "indexmap 2.7.0", "slab", "tokio", @@ -3153,9 +3154,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3180,7 +3181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -3191,7 +3192,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] @@ -3258,7 +3259,7 @@ dependencies = [ "futures-channel", "futures-util", "h2 0.4.7", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "httparse", "httpdate", @@ -3276,7 +3277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.1.0", + "http 1.2.0", "hyper 1.5.1", "hyper-util", "rustls", @@ -3311,7 +3312,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "hyper 1.5.1", "pin-project-lite", @@ -3770,7 +3771,7 @@ version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" dependencies = [ - "http 1.1.0", + "http 1.2.0", "serde", "serde_json", "thiserror 1.0.69", @@ -3826,8 +3827,9 @@ dependencies = [ [[package]] name = "kona-derive" -version = "0.1.0" -source = "git+https://github.com/anton-rs/kona?branch=rf/fix/executor-trait#83ac16adaa63deb4a35b47d2182f01994a696a62" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3ecefd72430904defb365ee4d7423734740a91704761ba7984ff839c13f20e" dependencies = [ "alloy-consensus 0.7.2", "alloy-eips 0.7.2", @@ -3845,8 +3847,9 @@ dependencies = [ [[package]] name = "kona-driver" -version = "0.1.0" -source = "git+https://github.com/anton-rs/kona?branch=rf/fix/executor-trait#83ac16adaa63deb4a35b47d2182f01994a696a62" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b989e1959d898b6856bf8991d4aac6ea93f999a5cb64a6483a925ffe881f413" dependencies = [ "alloy-consensus 0.7.2", "alloy-primitives", @@ -5756,7 +5759,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.4.7", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.5.1", @@ -8478,9 +8481,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -8718,7 +8721,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand", diff --git a/Cargo.toml b/Cargo.toml index 403f4fe..d6e16fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,9 @@ redundant-clone = "warn" all-features = true rustdoc-args = ["--cfg", "docsrs"] -[patch.crates-io] -kona-derive = { git = "https://github.com/anton-rs/kona", branch = "rf/fix/executor-trait" } -kona-driver = { git = "https://github.com/anton-rs/kona", branch = "rf/fix/executor-trait" } +# [patch.crates-io] +# kona-derive = { git = "https://github.com/anton-rs/kona", branch = "main" } +# kona-driver = { git = "https://github.com/anton-rs/kona", branch = "main" } [workspace.dependencies] # Workspace @@ -51,8 +51,8 @@ hilo-providers-local = { version = "0.11.0", path = "crates/providers-local", de hilo-providers-alloy = { version = "0.11.0", path = "crates/providers-alloy", default-features = false } # Kona -kona-derive = { version = "0.1.0", default-features = false } -kona-driver = { version = "0.1.0", default-features = false } +kona-derive = { version = "0.2.0", default-features = false } +kona-driver = { version = "0.2.0", default-features = false } # Alloy alloy-rlp = { version = "0.3.9", default-features = false } diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 8d2d0c3..b1608c2 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -25,7 +25,7 @@ alloy-consensus.workspace = true alloy-network.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types-eth.workspace = true -alloy-provider = { workspace = true, features = ["ipc", "reqwest"] } +alloy-provider = { workspace = true, features = ["ipc", "reqwest", "engine-api"] } alloy-primitives = { workspace = true, features = ["map"] } alloy-transport-http = { workspace = true, features = ["jwt-auth"] } alloy-rpc-types-engine = { workspace = true, features = ["jwt", "serde"] } diff --git a/crates/engine/src/client.rs b/crates/engine/src/client.rs index f6329d4..42c9f77 100644 --- a/crates/engine/src/client.rs +++ b/crates/engine/src/client.rs @@ -3,11 +3,12 @@ use alloy_eips::eip1898::BlockNumberOrTag; use alloy_network::AnyNetwork; use alloy_primitives::{Bytes, B256}; -use alloy_provider::{ReqwestProvider, RootProvider}; +use alloy_provider::{ReqwestProvider, RootProvider /* , ext::EngineApi */}; use alloy_rpc_client::RpcClient; use alloy_rpc_types_engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadInputV2, ExecutionPayloadV2, ExecutionPayloadV3, - ForkchoiceState, ForkchoiceUpdated, JwtSecret, PayloadId, PayloadStatus, + ExecutionPayloadEnvelopeV2, ExecutionPayloadFieldV2, ExecutionPayloadInputV2, + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, + JwtSecret, PayloadId, PayloadStatus, PayloadStatusEnum, }; use alloy_transport_http::{ hyper_util::{ @@ -19,7 +20,7 @@ use alloy_transport_http::{ use async_trait::async_trait; use http_body_util::Full; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BatchValidationProvider, L2BlockInfo}; +use op_alloy_protocol::{BatchValidationProvider, BlockInfo, L2BlockInfo}; use op_alloy_provider::ext::engine::OpEngineApi; use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelopeV3, OpPayloadAttributes}; use std::sync::Arc; @@ -40,6 +41,8 @@ pub struct EngineClient { engine: RootProvider, AnyNetwork>, /// The L2 chain provider. rpc: AlloyL2ChainProvider, + /// The [RollupConfig] for the chain used to timestamp which version of the engine api to use. + cfg: Arc, } impl EngineClient { @@ -56,8 +59,99 @@ impl EngineClient { let engine = RootProvider::<_, AnyNetwork>::new(rpc_client); let rpc = ReqwestProvider::new_http(rpc); - let rpc = AlloyL2ChainProvider::new(rpc, cfg); - Self { engine, rpc } + let rpc = AlloyL2ChainProvider::new(rpc, cfg.clone()); + Self { engine, rpc, cfg } + } + + /// Returns which fork choice version to use based on the timestamp + /// and rollup config. + pub fn fork_choice_version(&self, timestamp: u64) -> u64 { + // TODO: replace this with https://github.com/alloy-rs/op-alloy/pull/321 + // once it's merged and updated in kona. + if self.cfg.ecotone_time.is_some_and(|t| timestamp >= t) { + // Cancun + 3 + } else if self.cfg.canyon_time.is_some_and(|t| timestamp >= t) { + // Shanghai + 2 + } else { + 1 + } + } + + /// Accepts a given payload. + /// Sends [OpPayloadAttributes] via a `ForkChoiceUpdated` message to the [Engine]. + /// If the payload is valid, the engine will create a new block and update the `safe_head`, + /// `safe_epoch`, and `unsafe_head`. + pub async fn accept_payload( + &mut self, + forkchoice: ForkchoiceState, + attributes: OpPayloadAttributes, + ) -> Result { + let timestamp = attributes.payload_attributes.timestamp; + let update = self.forkchoice_update(forkchoice, Some(attributes)).await?; + + if !update.payload_status.status.is_valid() { + return Err(EngineError::InvalidForkChoiceAttributes); + } + + let id = update.payload_id.ok_or(EngineError::MissingPayloadId)?; + + match self.fork_choice_version(timestamp) { + 1 => self.accept_v1(id).await, + 2 => self.accept_v2(id).await, + 3 => self.accept_v3(id).await, + _ => Err(EngineError::InvalidNewPayloadAttributes), + } + } + + /// Gets and marks a new payload for the V1 engine api. + pub async fn accept_v1(&mut self, _id: PayloadId) -> Result { + unimplemented!("v1 not supported by OpEngineApi") + } + + /// Gets and marks a new payload for the V2 engine api. + pub async fn accept_v2(&mut self, id: PayloadId) -> Result { + let payload = self.get_payload_v2(id).await?; + + let withdrawals = match &payload.execution_payload { + ExecutionPayloadFieldV2::V2(ExecutionPayloadV2 { withdrawals, .. }) => { + withdrawals.clone() + } + ExecutionPayloadFieldV2::V1(_) => vec![], + }; + let payload_inner = payload.into_v1_payload(); + let block_info = BlockInfo { + number: payload_inner.block_number, + hash: payload_inner.block_hash, + parent_hash: payload_inner.parent_hash, + timestamp: payload_inner.timestamp, + }; + let payload = ExecutionPayloadV2 { payload_inner, withdrawals }; + let status = self.new_payload_v2(payload.clone()).await?; + if !status.is_valid() && status.status != PayloadStatusEnum::Accepted { + return Err(EngineError::InvalidNewPayloadAttributes); + } + + Ok(block_info) + } + + /// Gets and marks a new payload for the V3 engine api. + pub async fn accept_v3(&mut self, id: PayloadId) -> Result { + let payload = self.get_payload_v3(id).await?; + + let block_info = BlockInfo { + number: payload.execution_payload.payload_inner.payload_inner.block_number, + hash: payload.execution_payload.payload_inner.payload_inner.block_hash, + parent_hash: payload.execution_payload.payload_inner.payload_inner.parent_hash, + timestamp: payload.execution_payload.payload_inner.payload_inner.timestamp, + }; + let status = self.new_payload_v3(payload.execution_payload, block_info.hash).await?; + if !status.is_valid() && status.status != PayloadStatusEnum::Accepted { + return Err(EngineError::InvalidNewPayloadAttributes); + } + + Ok(block_info) } } @@ -65,6 +159,13 @@ impl EngineClient { impl Engine for EngineClient { type Error = EngineError; + async fn get_payload_v1( + &self, + _payload_id: PayloadId, + ) -> Result { + unimplemented!("v1 not supported by OpEngineApi") + } + async fn get_payload_v2( &self, payload_id: PayloadId, @@ -87,6 +188,13 @@ impl Engine for EngineClient { self.engine.fork_choice_updated_v2(state, attr).await.map_err(|_| EngineError::PayloadError) } + async fn new_payload_v1( + &self, + _payload: ExecutionPayloadV1, + ) -> Result { + unimplemented!("v1 not supported by OpEngineApi") + } + async fn new_payload_v2( &self, payload: ExecutionPayloadV2, diff --git a/crates/engine/src/controller.rs b/crates/engine/src/controller.rs index ea1ec28..f135762 100644 --- a/crates/engine/src/controller.rs +++ b/crates/engine/src/controller.rs @@ -4,7 +4,12 @@ use alloy_consensus::{Header, Sealed}; use alloy_primitives::B256; +use alloy_rpc_types_engine::{ + ExecutionPayloadEnvelopeV2, ExecutionPayloadFieldV2, ExecutionPayloadV2, ForkchoiceState, + JwtSecret, PayloadStatusEnum, +}; use async_trait::async_trait; +use hilo_providers_alloy::AlloyL2ChainProvider; use kona_driver::Executor; use op_alloy_consensus::OpBlock; use op_alloy_genesis::RollupConfig; @@ -14,31 +19,7 @@ use std::{sync::Arc, time::Duration}; use tokio::time::sleep; use url::Url; -use hilo_providers_alloy::AlloyL2ChainProvider; - -use alloy_rpc_types_engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadFieldV2, ExecutionPayloadV2, ForkchoiceState, - JwtSecret, PayloadStatusEnum, -}; - -use crate::{Engine, EngineClient, EngineControllerError}; - -/// L1 epoch block -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] -pub struct Epoch { - /// The block number - pub number: u64, - /// The block hash - pub hash: B256, - /// The block timestamp - pub timestamp: u64, -} - -impl From for Epoch { - fn from(block: BlockInfo) -> Self { - Self { number: block.number, hash: block.hash, timestamp: block.timestamp } - } -} +use crate::{Engine, EngineClient, EngineControllerError, Epoch}; /// The engine controller. #[derive(Debug, Clone)] @@ -149,22 +130,6 @@ impl EngineController { self.client.forkchoice_update(forkchoice, None).await.is_ok() } - /// Returns which fork choice version to use based on the timestamp - /// and rollup config. - pub fn fork_choice_version(&self, timestamp: u64) -> u64 { - // TODO: replace this with https://github.com/alloy-rs/op-alloy/pull/321 - // once it's merged and updated in kona. - if self.ecotone_timestamp.is_some_and(|t| timestamp >= t) { - // Cancun - 3 - } else if self.canyon_timestamp.is_some_and(|t| timestamp >= t) { - // Shanghai - 2 - } else { - 1 - } - } - /// Updates the forkchoice by sending `engine_forkchoiceUpdatedV2` (v3 post Ecotone) to the /// engine with no payload. async fn skip_attributes( @@ -181,47 +146,6 @@ impl EngineController { Ok(()) } - /// Sends [OpPayloadAttributes] via a `ForkChoiceUpdated` message to the [Engine]. - /// If the payload is valid, the engine will create a new block and update the `safe_head`, - /// `safe_epoch`, and `unsafe_head`. - async fn new_payload( - &self, - attributes: OpPayloadAttributes, - ) -> Result { - let forkchoice = self.create_forkchoice_state(); - - let update = self.client.forkchoice_update(forkchoice, Some(attributes)).await?; - - if !update.payload_status.status.is_valid() { - return Err(EngineControllerError::InvalidPayloadAttributes); - } - - let id = update.payload_id.ok_or(EngineControllerError::MissingPayloadId)?; - - let payload = self.client.get_payload_v2(id).await?; - - let withdrawals = match &payload.execution_payload { - ExecutionPayloadFieldV2::V2(ExecutionPayloadV2 { withdrawals, .. }) => { - withdrawals.clone() - } - ExecutionPayloadFieldV2::V1(_) => vec![], - }; - let payload_inner = payload.into_v1_payload(); - let block_info = BlockInfo { - number: payload_inner.block_number, - hash: payload_inner.block_hash, - parent_hash: payload_inner.parent_hash, - timestamp: payload_inner.timestamp, - }; - let payload = ExecutionPayloadV2 { payload_inner, withdrawals }; - let status = self.client.new_payload_v2(payload.clone()).await?; - if !status.is_valid() && status.status != PayloadStatusEnum::Accepted { - return Err(EngineControllerError::InvalidPayloadAttributes); - } - - Ok(block_info) - } - /// Initiates validation & production of a new block: /// - Sends the [OpPayloadAttributes] to the engine via `engine_forkchoiceUpdatedV2` (V3 post /// Ecotone) and retrieves the [ExecutionPayloadEnvelopeV2] @@ -234,7 +158,8 @@ impl EngineController { &mut self, attributes: OpPayloadAttributes, ) -> Result<(), EngineControllerError> { - let new_head = self.new_payload(attributes).await?; + let forkchoice = self.create_forkchoice_state(); + let new_head = self.client.accept_payload(forkchoice, attributes).await?; let new_epoch = new_head.into(); self.update_safe_head(new_head, new_epoch, true); self.update_forkchoice().await?; diff --git a/crates/engine/src/epoch.rs b/crates/engine/src/epoch.rs new file mode 100644 index 0000000..dde86ad --- /dev/null +++ b/crates/engine/src/epoch.rs @@ -0,0 +1,21 @@ +//! Contains an epoch type. + +use alloy_primitives::B256; +use op_alloy_protocol::BlockInfo; + +/// L1 epoch block +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Epoch { + /// The block number + pub number: u64, + /// The block hash + pub hash: B256, + /// The block timestamp + pub timestamp: u64, +} + +impl From for Epoch { + fn from(block: BlockInfo) -> Self { + Self { number: block.number, hash: block.hash, timestamp: block.timestamp } + } +} diff --git a/crates/engine/src/errors.rs b/crates/engine/src/errors.rs index aa2f67b..cf42fd2 100644 --- a/crates/engine/src/errors.rs +++ b/crates/engine/src/errors.rs @@ -20,6 +20,15 @@ pub enum EngineError { /// Failed to get the `L2BlockInfo` for the given block number. #[error("Failed to get the `L2BlockInfo` for the given block number")] L2BlockInfoFetch, + /// Invalid payload attributes were received from a fork choice update. + #[error("Invalid payload attributes were received from a fork choice update")] + InvalidForkChoiceAttributes, + /// Invalid payload attributes were received from a new payload method. + #[error("Invalid payload attributes were received from a new payload method")] + InvalidNewPayloadAttributes, + /// Missing payload id. + #[error("Missing payload id")] + MissingPayloadId, } /// An error that originated one level above the engine api, @@ -29,9 +38,6 @@ pub enum EngineControllerError { /// Invalid payload attributes were processed. #[error("Invalid payload attributes were processed")] InvalidPayloadAttributes, - /// Missing payload id. - #[error("Missing payload id")] - MissingPayloadId, /// An error from the engine api. #[error("An error from the engine api: {0}")] EngineError(#[from] EngineError), diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 6799e18..0bc8b83 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -15,6 +15,9 @@ pub use errors::{EngineControllerError, EngineError}; mod controller; pub use controller::EngineController; +mod epoch; +pub use epoch::Epoch; + mod client; pub use client::EngineClient; diff --git a/crates/engine/src/traits.rs b/crates/engine/src/traits.rs index 8c54bb6..cdc1535 100644 --- a/crates/engine/src/traits.rs +++ b/crates/engine/src/traits.rs @@ -3,8 +3,8 @@ use alloy_eips::eip1898::BlockNumberOrTag; use alloy_primitives::B256; use alloy_rpc_types_engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadV2, ExecutionPayloadV3, ForkchoiceState, - ForkchoiceUpdated, PayloadId, PayloadStatus, + ExecutionPayloadEnvelopeV2, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, + ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, }; use async_trait::async_trait; use op_alloy_protocol::L2BlockInfo; @@ -17,6 +17,12 @@ use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelopeV3, OpPayloadAttribute pub trait Engine { type Error: core::fmt::Debug; + /// Gets a payload for the given payload id. + async fn get_payload_v1( + &self, + payload_id: PayloadId, + ) -> Result; + /// Gets a payload for the given payload id. async fn get_payload_v2( &self, @@ -36,6 +42,12 @@ pub trait Engine { attr: Option, ) -> Result; + /// Creates a new payload with the given [ExecutionPayloadV1]. + async fn new_payload_v1( + &self, + payload: ExecutionPayloadV1, + ) -> Result; + /// Creates a new payload with the given [ExecutionPayloadV2]. async fn new_payload_v2( &self,