From 5535db318d699e7094b9706ad6b8ac4cd6fcea1d Mon Sep 17 00:00:00 2001 From: itschaindev Date: Fri, 11 Jul 2025 14:48:01 +0200 Subject: [PATCH 1/4] add superRootAtTimestamp e2e test, support for Go unmarshalling --- Cargo.lock | 1 + crates/supervisor/core/src/rpc/server.rs | 78 ++++++++-------- crates/supervisor/core/src/supervisor.rs | 28 +++--- crates/supervisor/rpc/Cargo.toml | 5 +- crates/supervisor/rpc/src/jsonrpsee.rs | 18 ++-- crates/supervisor/rpc/src/lib.rs | 4 +- crates/supervisor/rpc/src/response.rs | 92 ++++++++++++++++++- .../src/{chain_id.rs => hex_string_u64.rs} | 32 ++++--- crates/supervisor/types/src/lib.rs | 4 +- tests/supervisor/rpc_test.go | 41 +++++++++ 10 files changed, 222 insertions(+), 81 deletions(-) rename crates/supervisor/types/src/{chain_id.rs => hex_string_u64.rs} (56%) diff --git a/Cargo.lock b/Cargo.lock index ce3b556c20..f2e1232748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5080,6 +5080,7 @@ version = "0.1.0" dependencies = [ "alloy-eips", "alloy-primitives", + "alloy-serde", "jsonrpsee", "kona-interop", "kona-protocol", diff --git a/crates/supervisor/core/src/rpc/server.rs b/crates/supervisor/core/src/rpc/server.rs index 45467f3ca6..cbeb318596 100644 --- a/crates/supervisor/core/src/rpc/server.rs +++ b/crates/supervisor/core/src/rpc/server.rs @@ -5,12 +5,12 @@ use alloy_eips::eip1898::BlockNumHash; use alloy_primitives::{B256, ChainId, map::HashMap}; use async_trait::async_trait; use jsonrpsee::{core::RpcResult, types::ErrorObject}; -use kona_interop::{ - DependencySet, DerivedIdPair, ExecutingDescriptor, SafetyLevel, SuperRootOutput, -}; +use kona_interop::{DependencySet, DerivedIdPair, ExecutingDescriptor, SafetyLevel}; use kona_protocol::BlockInfo; -use kona_supervisor_rpc::{SupervisorApiServer, SupervisorChainSyncStatus, SupervisorSyncStatus}; -use kona_supervisor_types::{HexChainId, SuperHead}; +use kona_supervisor_rpc::{ + SuperRootOutputRpc, SupervisorApiServer, SupervisorChainSyncStatus, SupervisorSyncStatus, +}; +use kona_supervisor_types::{HexStringU64, SuperHead}; use std::sync::Arc; use tracing::{trace, warn}; @@ -39,7 +39,7 @@ where { async fn cross_derived_to_source( &self, - chain_id_hex: HexChainId, + chain_id_hex: HexStringU64, derived: BlockNumHash, ) -> RpcResult { let chain_id = ChainId::from(chain_id_hex); @@ -71,7 +71,7 @@ where ) } - async fn local_unsafe(&self, chain_id_hex: HexChainId) -> RpcResult { + async fn local_unsafe(&self, chain_id_hex: HexStringU64) -> RpcResult { let chain_id = ChainId::from(chain_id_hex); crate::observe_rpc_call!( "local_unsafe", @@ -101,7 +101,7 @@ where ) } - async fn cross_safe(&self, chain_id_hex: HexChainId) -> RpcResult { + async fn cross_safe(&self, chain_id_hex: HexStringU64) -> RpcResult { let chain_id = ChainId::from(chain_id_hex); crate::observe_rpc_call!( "cross_safe", @@ -120,7 +120,7 @@ where ) } - async fn finalized(&self, chain_id_hex: HexChainId) -> RpcResult { + async fn finalized(&self, chain_id_hex: HexStringU64) -> RpcResult { let chain_id = ChainId::from(chain_id_hex); crate::observe_rpc_call!( "finalized", @@ -147,22 +147,27 @@ where ) } - async fn super_root_at_timestamp(&self, timestamp: u64) -> RpcResult { + async fn super_root_at_timestamp( + &self, + timestamp_hex: HexStringU64, + ) -> RpcResult { crate::observe_rpc_call!( "super_root_at_timestamp", async { - trace!(target: "supervisor_rpc", - %timestamp, - "Received super_root_at_timestamp request" - ); - - self.supervisor.super_root_at_timestamp(timestamp) - .await - .map_err(|err| { - warn!(target: "supervisor_rpc", %err, "Error from core supervisor super_root_at_timestamp"); - ErrorObject::from(err) - }) - }.await) + let timestamp = u64::from(timestamp_hex); + trace!(target: "supervisor_rpc", + %timestamp, + "Received super_root_at_timestamp request" + ); + + self.supervisor.super_root_at_timestamp(timestamp) + .await + .map_err(|err| { + warn!(target: "supervisor_rpc", %err, "Error from core supervisor super_root_at_timestamp"); + ErrorObject::from(err) + }) + }.await + ) } async fn check_access_list( @@ -175,19 +180,20 @@ where crate::observe_rpc_call!( "check_access_list", async { - trace!(target: "supervisor_rpc", - num_inbox_entries = inbox_entries.len(), - ?min_safety, - ?executing_descriptor, - "Received check_access_list request", - ); - self.supervisor - .check_access_list(inbox_entries, min_safety, executing_descriptor) - .map_err(|err| { - warn!(target: "supervisor_rpc", %err, "Error from core supervisor check_access_list"); - ErrorObject::from(err) - }) - }.await) + trace!(target: "supervisor_rpc", + num_inbox_entries = inbox_entries.len(), + ?min_safety, + ?executing_descriptor, + "Received check_access_list request", + ); + self.supervisor + .check_access_list(inbox_entries, min_safety, executing_descriptor) + .map_err(|err| { + warn!(target: "supervisor_rpc", %err, "Error from core supervisor check_access_list"); + ErrorObject::from(err) + }) + }.await + ) } async fn sync_status(&self) -> RpcResult { @@ -361,7 +367,7 @@ mod tests { async fn super_root_at_timestamp( &self, _timestamp: u64, - ) -> Result { + ) -> Result { unimplemented!() } } diff --git a/crates/supervisor/core/src/supervisor.rs b/crates/supervisor/core/src/supervisor.rs index 6ed1d8d579..a0844ae032 100644 --- a/crates/supervisor/core/src/supervisor.rs +++ b/crates/supervisor/core/src/supervisor.rs @@ -6,15 +6,16 @@ use alloy_rpc_client::RpcClient; use async_trait::async_trait; use core::fmt::Debug; use kona_interop::{ - ChainRootInfo, DependencySet, ExecutingDescriptor, OutputRootWithChain, SUPER_ROOT_VERSION, - SafetyLevel, SuperRoot, SuperRootOutput, + DependencySet, ExecutingDescriptor, OutputRootWithChain, SUPER_ROOT_VERSION, SafetyLevel, + SuperRoot, }; use kona_protocol::BlockInfo; +use kona_supervisor_rpc::{ChainRootInfoRpc, SuperRootOutputRpc}; use kona_supervisor_storage::{ ChainDb, ChainDbFactory, DerivationStorageReader, DerivationStorageWriter, FinalizedL1Storage, HeadRefStorageReader, LogStorageReader, LogStorageWriter, }; -use kona_supervisor_types::{SuperHead, parse_access_list}; +use kona_supervisor_types::{HexStringU64, SuperHead, parse_access_list}; use op_alloy_rpc_types::SuperchainDAError; use reqwest::Url; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -86,7 +87,7 @@ pub trait SupervisorService: Debug + Send + Sync { async fn super_root_at_timestamp( &self, timestamp: u64, - ) -> Result; + ) -> Result; /// Verifies if an access-list references only valid messages fn check_access_list( @@ -393,12 +394,12 @@ impl SupervisorService for Supervisor { async fn super_root_at_timestamp( &self, timestamp: u64, - ) -> Result { + ) -> Result { let mut chain_ids = self.config.dependency_set.dependencies.keys().collect::>(); // Sorting chain ids for deterministic super root hash chain_ids.sort(); - let mut chain_infos = Vec::::with_capacity(chain_ids.len()); + let mut chain_infos = Vec::::with_capacity(chain_ids.len()); let mut super_root_chains = Vec::::with_capacity(chain_ids.len()); let mut cross_safe_source = BlockNumHash::default(); @@ -413,8 +414,8 @@ impl SupervisorService for Supervisor { serde_json::to_string(&pending_output_v0).unwrap().as_bytes(), ); - chain_infos.push(ChainRootInfo { - chain_id: *id, + chain_infos.push(ChainRootInfoRpc { + chain_id: HexStringU64::from(*id), canonical: canonical_root, pending: pending_output_v0_bytes, }); @@ -424,11 +425,9 @@ impl SupervisorService for Supervisor { let l2_block = managed_node.l2_block_ref_by_timestamp(timestamp).await?; let source = self - .get_db(*id)? - .derived_to_source(l2_block.id()) - .map_err(|err| { + .derived_to_source_block(*id, l2_block.id()) + .inspect_err(|err| { error!(target: "supervisor_service", %id, %err, "Failed to get derived to source block for chain"); - SpecError::from(err) })?; if cross_safe_source.number == 0 || cross_safe_source.number < source.number { @@ -437,12 +436,11 @@ impl SupervisorService for Supervisor { } let super_root = SuperRoot { timestamp, output_roots: super_root_chains }; - let super_root_hash = super_root.hash(); - Ok(SuperRootOutput { + Ok(SuperRootOutputRpc { cross_safe_derived_from: cross_safe_source, - timestamp, + timestamp: timestamp.into(), super_root: super_root_hash, chains: chain_infos, version: SUPER_ROOT_VERSION, diff --git a/crates/supervisor/rpc/Cargo.toml b/crates/supervisor/rpc/Cargo.toml index 8e4d6f4095..8c5c35b7eb 100644 --- a/crates/supervisor/rpc/Cargo.toml +++ b/crates/supervisor/rpc/Cargo.toml @@ -23,16 +23,15 @@ kona-supervisor-types.workspace = true # jsonrpsee serde.workspace = true +serde_json.workspace = true jsonrpsee = { workspace = true, optional = true, features = ["macros", "server"] } # Alloy alloy-eips.workspace = true +alloy-serde.workspace = true alloy-primitives = { workspace = true, features = ["map", "rlp", "serde"] } op-alloy-consensus.workspace = true -#[dev-dependencies] -serde_json.workspace = true - [features] serde = [ "alloy-eips/serde", diff --git a/crates/supervisor/rpc/src/jsonrpsee.rs b/crates/supervisor/rpc/src/jsonrpsee.rs index ada988367e..693ee6223d 100644 --- a/crates/supervisor/rpc/src/jsonrpsee.rs +++ b/crates/supervisor/rpc/src/jsonrpsee.rs @@ -5,16 +5,15 @@ pub use jsonrpsee::{ types::{ErrorCode, ErrorObjectOwned}, }; -use crate::SupervisorSyncStatus; +use crate::{SuperRootOutputRpc, SupervisorSyncStatus}; use alloy_eips::BlockNumHash; use alloy_primitives::{B256, BlockHash, ChainId, map::HashMap}; use jsonrpsee::proc_macros::rpc; use kona_interop::{ DependencySet, DerivedIdPair, DerivedRefPair, ExecutingDescriptor, ManagedEvent, SafetyLevel, - SuperRootOutput, }; use kona_protocol::BlockInfo; -use kona_supervisor_types::{BlockSeal, HexChainId, OutputV0, Receipts, SubscriptionEvent}; +use kona_supervisor_types::{BlockSeal, HexStringU64, OutputV0, Receipts, SubscriptionEvent}; use serde::{Deserialize, Serialize}; /// Supervisor API for interop. @@ -28,7 +27,7 @@ pub trait SupervisorApi { #[method(name = "crossDerivedToSource")] async fn cross_derived_to_source( &self, - chain_id: HexChainId, + chain_id: HexStringU64, block_id: BlockNumHash, ) -> RpcResult; @@ -38,7 +37,7 @@ pub trait SupervisorApi { /// /// [`LocalUnsafe`]: SafetyLevel::LocalUnsafe #[method(name = "localUnsafe")] - async fn local_unsafe(&self, chain_id: HexChainId) -> RpcResult; + async fn local_unsafe(&self, chain_id: HexStringU64) -> RpcResult; /// Returns the [`CrossSafe`] block for given chain. /// @@ -46,7 +45,7 @@ pub trait SupervisorApi { /// /// [`CrossSafe`]: SafetyLevel::CrossSafe #[method(name = "crossSafe")] - async fn cross_safe(&self, chain_id: HexChainId) -> RpcResult; + async fn cross_safe(&self, chain_id: HexStringU64) -> RpcResult; /// Returns the [`Finalized`] block for the given chain. /// @@ -54,7 +53,7 @@ pub trait SupervisorApi { /// /// [`Finalized`]: SafetyLevel::Finalized #[method(name = "finalized")] - async fn finalized(&self, chain_id: HexChainId) -> RpcResult; + async fn finalized(&self, chain_id: HexStringU64) -> RpcResult; /// Returns the finalized L1 block that the supervisor is synced to. /// @@ -75,7 +74,10 @@ pub trait SupervisorApi { /// [`SuperRoot`]: kona_interop::SuperRoot /// [`ChainRootInfo`]: kona_interop::ChainRootInfo #[method(name = "superRootAtTimestamp")] - async fn super_root_at_timestamp(&self, timestamp: u64) -> RpcResult; + async fn super_root_at_timestamp( + &self, + timestamp: HexStringU64, + ) -> RpcResult; /// Verifies if an access-list references only valid messages w.r.t. locally configured minimum /// [`SafetyLevel`]. diff --git a/crates/supervisor/rpc/src/lib.rs b/crates/supervisor/rpc/src/lib.rs index 054a89018c..0ba13757a7 100644 --- a/crates/supervisor/rpc/src/lib.rs +++ b/crates/supervisor/rpc/src/lib.rs @@ -11,6 +11,8 @@ pub use jsonrpsee::SupervisorApiServer; pub use jsonrpsee::ManagedModeApiClient; pub mod response; -pub use response::{SupervisorChainSyncStatus, SupervisorSyncStatus}; +pub use response::{ + ChainRootInfoRpc, SuperRootOutputRpc, SupervisorChainSyncStatus, SupervisorSyncStatus, +}; pub use kona_protocol::BlockInfo; diff --git a/crates/supervisor/rpc/src/response.rs b/crates/supervisor/rpc/src/response.rs index 7a6ce100fc..f20e0d0878 100644 --- a/crates/supervisor/rpc/src/response.rs +++ b/crates/supervisor/rpc/src/response.rs @@ -1,9 +1,10 @@ //! Supervisor RPC response types. use alloy_eips::BlockNumHash; -use alloy_primitives::{ChainId, map::HashMap}; +use alloy_primitives::{B256, Bytes, ChainId, map::HashMap}; use kona_protocol::BlockInfo; -use kona_supervisor_types::SuperHead; +use kona_supervisor_types::{HexStringU64, SuperHead}; +use serde::{Deserialize, Serialize, Serializer}; /// Describes superchain sync status. /// @@ -80,10 +81,78 @@ impl From for SupervisorChainSyncStatus { } } +/// This is same as [`kona_interop::ChainRootInfo`] but with [HexStringU64] instead of [u64] for the +/// chain ID. +/// +/// Required by +/// [`super_root_at_timestamp`](crate::jsonrpsee::SupervisorApiServer::super_root_at_timestamp) RPC +/// for marshalling and unmarshalling in GO implementation. Required for e2e tests. +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ChainRootInfoRpc { + /// The chain ID. + #[serde(rename = "chainID")] + pub chain_id: HexStringU64, + /// The canonical output root of the latest canonical block at a particular timestamp. + pub canonical: B256, + /// The pending output root. + /// + /// This is the output root preimage for the latest block at a particular timestamp prior to + /// validation of executing messages. If the original block was valid, this will be the + /// preimage of the output root from the `canonical` array. If it was invalid, it will be + /// the output root preimage from the optimistic block deposited transaction added to the + /// deposit-only block. + pub pending: Bytes, +} + +/// This is same as [`kona_interop::SuperRootOutput`] but with [HexStringU64] instead of [u64] for +/// the timestamp. version is serialized as a hex string. +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SuperRootOutputRpc { + /// The Highest L1 Block that is cross-safe among all chains. + pub cross_safe_derived_from: BlockNumHash, + /// The timestamp of the super root. + pub timestamp: HexStringU64, + /// The super root hash. + pub super_root: B256, + /// The version of the super root. + #[serde(serialize_with = "serialize_u8_as_hex")] + pub version: u8, + /// The chain root info for each chain in the dependency set. + /// It represents the state of the chain at or before the timestamp. + pub chains: Vec, +} + +/// Serializes a [u8] as a hex string. Ensure that the hex string has an even length. +/// +/// This is used to serialize the [`SuperRootOutputRpc`]'s version field as a hex string. +fn serialize_u8_as_hex(value: &u8, serializer: S) -> Result +where + S: Serializer, +{ + // Use alloy_serde::quantity to get the hex string as a serde_json::Value + let json_value = alloy_serde::quantity::serialize(value, serde_json::value::Serializer) + .map_err(serde::ser::Error::custom)?; + + // Extract the string from the JSON value + let hex_string = json_value + .as_str() + .ok_or_else(|| serde::ser::Error::custom("Expected a string from alloy_serde::quantity"))?; + + // Ensure even length by adding leading zero if needed + let hex_part = hex_string.strip_prefix("0x").unwrap_or(hex_string); + let padded_hex = + if hex_part.len() % 2 == 1 { format!("0x0{}", hex_part) } else { hex_string.to_string() }; + + serializer.serialize_str(&padded_hex) +} + #[cfg(test)] mod test { use super::*; use alloy_primitives::b256; + use kona_interop::SUPER_ROOT_VERSION; const CHAIN_STATUS: &str = r#" { @@ -242,4 +311,23 @@ mod test { } ) } + + #[test] + fn test_super_root_version_even_length_hex() { + let root = SuperRootOutputRpc { + cross_safe_derived_from: BlockNumHash::default(), + timestamp: HexStringU64(0), + super_root: B256::default(), + version: SUPER_ROOT_VERSION, + chains: vec![], + }; + let json = serde_json::to_string(&root).expect("should serialize"); + let v: serde_json::Value = serde_json::from_str(&json).expect("valid json"); + let version_field = + v.get("version").expect("version field present").as_str().expect("version is string"); + let hex_part = &version_field[2..]; // remove 0x + assert_eq!(hex_part.len() % 2, 0, "Hex string should have even length"); + // For SUPER_ROOT_VERSION = 1, should be 0x01 + assert_eq!(version_field, "0x01"); + } } diff --git a/crates/supervisor/types/src/chain_id.rs b/crates/supervisor/types/src/hex_string_u64.rs similarity index 56% rename from crates/supervisor/types/src/chain_id.rs rename to crates/supervisor/types/src/hex_string_u64.rs index 03aefaf786..7df7641804 100644 --- a/crates/supervisor/types/src/chain_id.rs +++ b/crates/supervisor/types/src/hex_string_u64.rs @@ -1,11 +1,9 @@ -use alloy_primitives::ChainId; - -/// A wrapper around `ChainId` that supports hex string (e.g. `"0x1"`) or numeric deserialization +/// A wrapper around `u64` that supports hex string (e.g. `"0x1"`) or numeric deserialization /// for RPC inputs. -#[derive(Debug, Clone, Copy)] -pub struct HexChainId(pub ChainId); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct HexStringU64(pub u64); -impl serde::Serialize for HexChainId { +impl serde::Serialize for HexStringU64 { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -14,7 +12,7 @@ impl serde::Serialize for HexChainId { } } -impl<'de> serde::Deserialize<'de> for HexChainId { +impl<'de> serde::Deserialize<'de> for HexStringU64 { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -24,12 +22,18 @@ impl<'de> serde::Deserialize<'de> for HexChainId { } } -impl From for ChainId { - fn from(value: HexChainId) -> Self { +impl From for u64 { + fn from(value: HexStringU64) -> Self { value.0 } } +impl From for HexStringU64 { + fn from(value: u64) -> Self { + Self(value) + } +} + #[cfg(test)] mod tests { use super::*; @@ -37,23 +41,23 @@ mod tests { #[test] fn test_deserialize_from_hex_string() { let json = r#""0x1a""#; - let parsed: HexChainId = serde_json::from_str(json).expect("should parse hex string"); - let chain_id: ChainId = parsed.into(); + let parsed: HexStringU64 = serde_json::from_str(json).expect("should parse hex string"); + let chain_id: u64 = parsed.0; assert_eq!(chain_id, 0x1a); } #[test] fn test_serialize_to_hex() { - let value = HexChainId(26); + let value = HexStringU64(26); let json = serde_json::to_string(&value).expect("should serialize"); assert_eq!(json, r#""0x1a""#); } #[test] fn test_round_trip() { - let original = HexChainId(12345); + let original = HexStringU64(12345); let json = serde_json::to_string(&original).unwrap(); - let parsed: HexChainId = serde_json::from_str(&json).unwrap(); + let parsed: HexStringU64 = serde_json::from_str(&json).unwrap(); assert_eq!(parsed.0, original.0); } } diff --git a/crates/supervisor/types/src/lib.rs b/crates/supervisor/types/src/lib.rs index 202cd4f023..55e27f33fb 100644 --- a/crates/supervisor/types/src/lib.rs +++ b/crates/supervisor/types/src/lib.rs @@ -18,9 +18,9 @@ pub use receipt::Receipts; mod access_list; pub use access_list::{Access, AccessListError, parse_access_list}; -mod chain_id; +mod hex_string_u64; mod types; -pub use chain_id::HexChainId; +pub use hex_string_u64::HexStringU64; pub use types::{BlockSeal, OutputV0, SubscriptionEvent}; diff --git a/tests/supervisor/rpc_test.go b/tests/supervisor/rpc_test.go index 330cd6b692..663739a3ba 100644 --- a/tests/supervisor/rpc_test.go +++ b/tests/supervisor/rpc_test.go @@ -13,12 +13,15 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// TODO: add test for dependencySetV1 after devstack support is added to the QueryAPI + func TestRPCLocalUnsafe(gt *testing.T) { t := devtest.ParallelT(gt) sys := presets.NewSimpleInterop(t) @@ -85,6 +88,44 @@ func TestRPCFinalized(gt *testing.T) { } } +func TestRPCFinalizedL1(gt *testing.T) { + t := devtest.ParallelT(gt) + sys := presets.NewSimpleInterop(t) + client := sys.Supervisor.Escape() + t.Run("succeeds to get finalized L1 block", func(gt devtest.T) { + block, err := client.QueryAPI().FinalizedL1(context.Background()) + require.NoError(t, err) + assert.Greater(t, block.Number, uint64(0)) + assert.Less(t, block.Time, uint64(time.Now().Unix()+5)) + assert.Len(t, block.Hash, 32) + }) +} + +func TestRPCSuperRootAtTimestamp(gt *testing.T) { + t := devtest.ParallelT(gt) + sys := presets.NewSimpleInterop(t) + client := sys.Supervisor.Escape() + + t.Run("fails with invalid timestamp", func(gt devtest.T) { + _, err := client.QueryAPI().SuperRootAtTimestamp(context.Background(), 0) + require.Error(t, err) + }) + + t.Run("succeeds with valid timestamp", func(gt devtest.T) { + timeNow := uint64(time.Now().Unix()) + root, err := client.QueryAPI().SuperRootAtTimestamp(context.Background(), hexutil.Uint64(timeNow-90)) + require.NoError(t, err) + assert.Less(t, root.Timestamp, timeNow) + assert.Len(t, root.SuperRoot, 32) + assert.Len(t, root.Chains, 2) + + for _, chain := range root.Chains { + assert.Len(t, chain.Canonical, 32) + assert.Contains(t, []eth.ChainID{sys.L2ChainA.ChainID(), sys.L2ChainB.ChainID()}, chain.ChainID) + } + }) +} + func TestRPCAllSafeDerivedAt(gt *testing.T) { t := devtest.ParallelT(gt) From 6b2ae58a0bfbd1ab43f28ff00b030ad76e8e101c Mon Sep 17 00:00:00 2001 From: itschaindev Date: Fri, 11 Jul 2025 14:49:36 +0200 Subject: [PATCH 2/4] clean test --- tests/supervisor/rpc_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/supervisor/rpc_test.go b/tests/supervisor/rpc_test.go index 663739a3ba..b53e2efffc 100644 --- a/tests/supervisor/rpc_test.go +++ b/tests/supervisor/rpc_test.go @@ -115,7 +115,6 @@ func TestRPCSuperRootAtTimestamp(gt *testing.T) { timeNow := uint64(time.Now().Unix()) root, err := client.QueryAPI().SuperRootAtTimestamp(context.Background(), hexutil.Uint64(timeNow-90)) require.NoError(t, err) - assert.Less(t, root.Timestamp, timeNow) assert.Len(t, root.SuperRoot, 32) assert.Len(t, root.Chains, 2) From 5f9d59b7ef91926fed0bd0e587e63036f7ded3f0 Mon Sep 17 00:00:00 2001 From: itschaindev Date: Fri, 11 Jul 2025 17:03:58 +0200 Subject: [PATCH 3/4] simplified serialization of u8 --- crates/supervisor/core/src/supervisor.rs | 6 ++-- crates/supervisor/rpc/src/response.rs | 36 ++++++++---------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/crates/supervisor/core/src/supervisor.rs b/crates/supervisor/core/src/supervisor.rs index a0844ae032..25653e15d5 100644 --- a/crates/supervisor/core/src/supervisor.rs +++ b/crates/supervisor/core/src/supervisor.rs @@ -15,7 +15,7 @@ use kona_supervisor_storage::{ ChainDb, ChainDbFactory, DerivationStorageReader, DerivationStorageWriter, FinalizedL1Storage, HeadRefStorageReader, LogStorageReader, LogStorageWriter, }; -use kona_supervisor_types::{HexStringU64, SuperHead, parse_access_list}; +use kona_supervisor_types::{SuperHead, parse_access_list}; use op_alloy_rpc_types::SuperchainDAError; use reqwest::Url; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -415,7 +415,7 @@ impl SupervisorService for Supervisor { ); chain_infos.push(ChainRootInfoRpc { - chain_id: HexStringU64::from(*id), + chain_id: *id, canonical: canonical_root, pending: pending_output_v0_bytes, }); @@ -440,7 +440,7 @@ impl SupervisorService for Supervisor { Ok(SuperRootOutputRpc { cross_safe_derived_from: cross_safe_source, - timestamp: timestamp.into(), + timestamp, super_root: super_root_hash, chains: chain_infos, version: SUPER_ROOT_VERSION, diff --git a/crates/supervisor/rpc/src/response.rs b/crates/supervisor/rpc/src/response.rs index f20e0d0878..0cb3b4e019 100644 --- a/crates/supervisor/rpc/src/response.rs +++ b/crates/supervisor/rpc/src/response.rs @@ -3,7 +3,7 @@ use alloy_eips::BlockNumHash; use alloy_primitives::{B256, Bytes, ChainId, map::HashMap}; use kona_protocol::BlockInfo; -use kona_supervisor_types::{HexStringU64, SuperHead}; +use kona_supervisor_types::SuperHead; use serde::{Deserialize, Serialize, Serializer}; /// Describes superchain sync status. @@ -81,8 +81,8 @@ impl From for SupervisorChainSyncStatus { } } -/// This is same as [`kona_interop::ChainRootInfo`] but with [HexStringU64] instead of [u64] for the -/// chain ID. +/// This is same as [`kona_interop::ChainRootInfo`] but with [`u64`] serializeing as a valid hex +/// string. /// /// Required by /// [`super_root_at_timestamp`](crate::jsonrpsee::SupervisorApiServer::super_root_at_timestamp) RPC @@ -91,8 +91,8 @@ impl From for SupervisorChainSyncStatus { #[serde(rename_all = "camelCase")] pub struct ChainRootInfoRpc { /// The chain ID. - #[serde(rename = "chainID")] - pub chain_id: HexStringU64, + #[serde(rename = "chainID", with = "alloy_serde::quantity")] + pub chain_id: u64, /// The canonical output root of the latest canonical block at a particular timestamp. pub canonical: B256, /// The pending output root. @@ -105,15 +105,16 @@ pub struct ChainRootInfoRpc { pub pending: Bytes, } -/// This is same as [`kona_interop::SuperRootOutput`] but with [HexStringU64] instead of [u64] for -/// the timestamp. version is serialized as a hex string. +/// This is same as [`kona_interop::SuperRootOutput`] but with timestamp serializing as a valid hex +/// string. version is also serialized as an even length hex string. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SuperRootOutputRpc { /// The Highest L1 Block that is cross-safe among all chains. pub cross_safe_derived_from: BlockNumHash, /// The timestamp of the super root. - pub timestamp: HexStringU64, + #[serde(with = "alloy_serde::quantity")] + pub timestamp: u64, /// The super root hash. pub super_root: B256, /// The version of the super root. @@ -131,21 +132,8 @@ fn serialize_u8_as_hex(value: &u8, serializer: S) -> Result where S: Serializer, { - // Use alloy_serde::quantity to get the hex string as a serde_json::Value - let json_value = alloy_serde::quantity::serialize(value, serde_json::value::Serializer) - .map_err(serde::ser::Error::custom)?; - - // Extract the string from the JSON value - let hex_string = json_value - .as_str() - .ok_or_else(|| serde::ser::Error::custom("Expected a string from alloy_serde::quantity"))?; - - // Ensure even length by adding leading zero if needed - let hex_part = hex_string.strip_prefix("0x").unwrap_or(hex_string); - let padded_hex = - if hex_part.len() % 2 == 1 { format!("0x0{}", hex_part) } else { hex_string.to_string() }; - - serializer.serialize_str(&padded_hex) + let hex_string = format!("0x{:02x}", value); + serializer.serialize_str(&hex_string) } #[cfg(test)] @@ -316,7 +304,7 @@ mod test { fn test_super_root_version_even_length_hex() { let root = SuperRootOutputRpc { cross_safe_derived_from: BlockNumHash::default(), - timestamp: HexStringU64(0), + timestamp: 0, super_root: B256::default(), version: SUPER_ROOT_VERSION, chains: vec![], From 589e07629a1b9a5e2c496dcf61de336de8485f3f Mon Sep 17 00:00:00 2001 From: itschaindev Date: Fri, 11 Jul 2025 21:17:39 +0200 Subject: [PATCH 4/4] use ChainId instead of u64 --- crates/supervisor/rpc/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/supervisor/rpc/src/response.rs b/crates/supervisor/rpc/src/response.rs index 0cb3b4e019..4d9443e566 100644 --- a/crates/supervisor/rpc/src/response.rs +++ b/crates/supervisor/rpc/src/response.rs @@ -92,7 +92,7 @@ impl From for SupervisorChainSyncStatus { pub struct ChainRootInfoRpc { /// The chain ID. #[serde(rename = "chainID", with = "alloy_serde::quantity")] - pub chain_id: u64, + pub chain_id: ChainId, /// The canonical output root of the latest canonical block at a particular timestamp. pub canonical: B256, /// The pending output root.