diff --git a/README.md b/README.md index b0a216f..911aaef 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ Morph provides a custom L2 Engine API (different from the standard Ethereum Engi | `engine_newL2Block` | Import a new L2 block via `newPayload` + `forkchoiceUpdated` and advance the canonical head | | `engine_newSafeL2Block` | Rebuild and import a safe L2 block from derivation inputs | | `engine_setBlockTags` | Update safe/finalized block tags without importing a block | +| `engine_appendBatchSignature` | Accept a BLS batch signature from the consensus layer (no-op for sync nodes) | ## Contributing diff --git a/crates/engine-api/src/api.rs b/crates/engine-api/src/api.rs index f5c9082..fbcfc3d 100644 --- a/crates/engine-api/src/api.rs +++ b/crates/engine-api/src/api.rs @@ -6,7 +6,9 @@ use crate::EngineApiResult; use alloy_primitives::B256; -use morph_payload_types::{AssembleL2BlockParams, ExecutableL2Data, GenericResponse, SafeL2Data}; +use morph_payload_types::{ + AssembleL2BlockParams, BatchSignature, ExecutableL2Data, GenericResponse, SafeL2Data, +}; use morph_primitives::MorphHeader; /// Morph L2 Engine API trait. @@ -107,4 +109,16 @@ pub trait MorphL2EngineApi: Send + Sync { safe_block_hash: B256, finalized_block_hash: B256, ) -> EngineApiResult<()>; + + /// Append a BLS batch signature for a given batch hash. + /// + /// Called by the consensus layer when collecting validator signatures for + /// L1 batch submission. For non-sequencer sync nodes, this is a no-op. + async fn append_batch_signature( + &self, + _batch_hash: B256, + _signature: BatchSignature, + ) -> EngineApiResult<()> { + Ok(()) + } } diff --git a/crates/engine-api/src/rpc.rs b/crates/engine-api/src/rpc.rs index 3282e17..90b6ed5 100644 --- a/crates/engine-api/src/rpc.rs +++ b/crates/engine-api/src/rpc.rs @@ -6,7 +6,9 @@ use crate::{EngineApiResult, api::MorphL2EngineApi}; use alloy_primitives::B256; use jsonrpsee::{RpcModule, core::RpcResult, proc_macros::rpc}; -use morph_payload_types::{AssembleL2BlockParams, ExecutableL2Data, GenericResponse, SafeL2Data}; +use morph_payload_types::{ + AssembleL2BlockParams, BatchSignature, ExecutableL2Data, GenericResponse, SafeL2Data, +}; use morph_primitives::MorphHeader; use reth_rpc_api::IntoEngineApiRpcModule; use std::sync::Arc; @@ -61,6 +63,22 @@ pub trait MorphL2EngineRpc { safe_block_hash: B256, finalized_block_hash: B256, ) -> RpcResult<()>; + + /// Append a BLS batch signature for a given batch hash. + /// + /// Called by the consensus layer when collecting validator signatures for + /// L1 batch submission. Non-sequencer sync nodes accept this call but do + /// not persist the signature. + /// + /// # JSON-RPC Method + /// + /// `engine_appendBatchSignature` + #[method(name = "appendBatchSignature")] + async fn append_batch_signature( + &self, + batch_hash: B256, + signature: BatchSignature, + ) -> RpcResult<()>; } /// Implementation of the L2 Engine RPC API. @@ -164,6 +182,25 @@ where e.into() }) } + + async fn append_batch_signature( + &self, + batch_hash: B256, + _signature: BatchSignature, + ) -> RpcResult<()> { + tracing::debug!( + target: "morph::engine", + %batch_hash, + "RPC appendBatchSignature called (no-op for sync node)" + ); + self.inner + .append_batch_signature(batch_hash, _signature) + .await + .map_err(|e| { + tracing::error!(target: "morph::engine", error = %e, "failed to append batch signature"); + e.into() + }) + } } /// Converts an `EngineApiResult` into a `RpcResult`. diff --git a/crates/payload/types/src/lib.rs b/crates/payload/types/src/lib.rs index a6159f8..3b51724 100644 --- a/crates/payload/types/src/lib.rs +++ b/crates/payload/types/src/lib.rs @@ -35,7 +35,7 @@ use reth_ethereum_primitives as _; pub use attributes::{MorphPayloadAttributes, MorphPayloadBuilderAttributes}; pub use built::MorphBuiltPayload; pub use executable_l2_data::ExecutableL2Data; -pub use params::{AssembleL2BlockParams, GenericResponse}; +pub use params::{AssembleL2BlockParams, BatchSignature, GenericResponse}; pub use safe_l2_data::SafeL2Data; // ============================================================================= diff --git a/crates/payload/types/src/params.rs b/crates/payload/types/src/params.rs index 77343b4..4aa66a8 100644 --- a/crates/payload/types/src/params.rs +++ b/crates/payload/types/src/params.rs @@ -1,6 +1,6 @@ //! Request/response types for L2 Engine API methods. -use alloy_primitives::Bytes; +use alloy_primitives::{Address, Bytes}; /// Parameters for engine_assembleL2Block. /// @@ -70,6 +70,22 @@ impl GenericResponse { } } +/// BLS batch signature from a sequencer validator. +/// +/// This is sent by the consensus layer via `engine_appendBatchSignature` +/// when collecting validator signatures for L1 batch submission. +/// For non-sequencer nodes this is accepted but not persisted. +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchSignature { + /// Validator address that produced the signature. + pub signer: Address, + /// BLS public key of the signer. + pub signer_pub_key: Bytes, + /// BLS signature over the batch hash. + pub signature: Bytes, +} + impl From for GenericResponse { fn from(success: bool) -> Self { Self { success }