From fe2590b360f4599f5429eccc42d49ae3e1b867b2 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:22:25 +0200 Subject: [PATCH 01/16] feat(provider): OP engine api trait ext + superchain signal type --- Cargo.toml | 2 + crates/provider/Cargo.toml | 47 ++++ crates/provider/README.md | 3 + crates/provider/src/ext/engine.rs | 308 ++++++++++++++++++++++ crates/provider/src/ext/mod.rs | 4 + crates/provider/src/lib.rs | 19 ++ crates/rpc-types-engine/Cargo.toml | 1 + crates/rpc-types-engine/src/lib.rs | 3 + crates/rpc-types-engine/src/superchain.rs | 146 ++++++++++ 9 files changed, 533 insertions(+) create mode 100644 crates/provider/Cargo.toml create mode 100644 crates/provider/README.md create mode 100644 crates/provider/src/ext/engine.rs create mode 100644 crates/provider/src/ext/mod.rs create mode 100644 crates/provider/src/lib.rs create mode 100644 crates/rpc-types-engine/src/superchain.rs diff --git a/Cargo.toml b/Cargo.toml index d03e5a4e..771b0763 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,8 @@ alloy-rpc-types-eth = { version = "0.3.6", default-features = false } alloy-eips = { version = "0.3.6", default-features = false } alloy-serde = { version = "0.3.6", default-features = false } alloy-signer = { version = "0.3.6", default-features = false } +alloy-provider = { version = "0.3.6", default-features = false } +alloy-transport = { version = "0.3.6", default-features = false } # Serde serde_repr = "0.1" diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml new file mode 100644 index 00000000..e45e5e60 --- /dev/null +++ b/crates/provider/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "op-alloy-provider" +description = "Interface with an OP Stack blockchain" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +authors.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +# Workspace +op-alloy-rpc-types-engine = { workspace = true, features = ["serde"] } + +# Alloy +alloy-primitives = { workspace = true, features = ["rlp", "serde"] } +alloy-rpc-types-engine = { workspace = true, features = ["serde"] } +alloy-provider.workspace = true +alloy-network.workspace = true +alloy-transport.workspace = true + +# arbitrary +arbitrary = { workspace = true, features = ["derive"], optional = true } + +# misc +async-trait = "0.1.82" + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["arbitrary"] } +alloy-consensus = { workspace = true, features = ["arbitrary"] } +alloy-rpc-types = { workspace = true, features = ["arbitrary"] } +arbitrary = { workspace = true, features = ["derive"] } +rand.workspace = true + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", +] +arbitrary = [ + "std", + "dep:arbitrary", + "alloy-primitives/arbitrary", +] diff --git a/crates/provider/README.md b/crates/provider/README.md new file mode 100644 index 00000000..5d91db07 --- /dev/null +++ b/crates/provider/README.md @@ -0,0 +1,3 @@ +# alloy-provider + +Interface with an OP Stack blockchain. diff --git a/crates/provider/src/ext/engine.rs b/crates/provider/src/ext/engine.rs new file mode 100644 index 00000000..1919445d --- /dev/null +++ b/crates/provider/src/ext/engine.rs @@ -0,0 +1,308 @@ +use alloy_network::Network; +use alloy_primitives::{BlockHash, B256}; +use alloy_provider::Provider; +use alloy_rpc_types_engine::{ + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadEnvelopeV2, ExecutionPayloadInputV2, + ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, + PayloadStatus, +}; +use alloy_transport::{Transport, TransportResult}; +use op_alloy_rpc_types_engine::{ + OptimismExecutionPayloadEnvelopeV3, OptimismExecutionPayloadEnvelopeV4, + OptimismPayloadAttributes, ProtocolVersion, SuperchainSignal, +}; + +/// Extension trait that gives access to Optimism engine API RPC methods. +/// +/// Note: +/// > The provider should use a JWT authentication layer. +/// +/// This follows the Optimism specs that can be found at: +/// +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +pub trait OpEngineApi: Send + Sync { + /// Sends the given payload to the execution layer client, as specified for the Shanghai fork. + /// + /// See also + /// + /// No modifications needed for OP compatibility. + async fn new_payload_v2( + &self, + payload: ExecutionPayloadInputV2, + ) -> TransportResult; + + /// Sends the given payload to the execution layer client, as specified for the Cancun fork. + /// + /// See also + /// + /// OP modifications: + /// - expected versioned hashes MUST be an empty array: therefore the `versioned_hashes` + /// parameter is removed. + /// - parent beacon block root MUST be the parent beacon block root from the L1 origin block of + /// the L2 block. + async fn new_payload_v3( + &self, + payload: ExecutionPayloadV3, + parent_beacon_block_root: B256, + ) -> TransportResult; + + /// Sends the given payload to the execution layer client, as specified for the Prague fork. + /// + /// See also + /// + /// OP modifications: TODO + async fn new_payload_v4( + &self, + payload: ExecutionPayloadV4, + parent_beacon_block_root: B256, + ) -> TransportResult; + + /// Updates the execution layer client with the given fork choice, as specified for the Shanghai + /// fork. + /// + /// Caution: This should not accept the `parentBeaconBlockRoot` field in the payload attributes. + /// + /// See also + /// + /// OP modifications: + /// - The `payload_attributes` parameter is extended with the `OptimismPayloadAttributes` type + /// as described in + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult; + + /// Updates the execution layer client with the given fork choice, as specified for the Cancun + /// fork. + /// + /// See also + /// + /// OP modifications: + /// - Must be called with an Ecotone payload + /// - Attributes must contain the parent beacon block root field + /// - The `payload_attributes` parameter is extended with the `OptimismPayloadAttributes` type + /// as described in + async fn fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult; + + /// Retrieves an execution payload from a previously started build process, as specified for the + /// Shanghai fork. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// No modifications needed for OP compatibility. + async fn get_payload_v2( + &self, + payload_id: PayloadId, + ) -> TransportResult; + + /// Retrieves an execution payload from a previously started build process, as specified for the + /// Cancun fork. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// OP modifications: + /// - the response type is extended to [`OptimismExecutionPayloadEnvelopeV3`]. + async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> TransportResult; + + /// Returns the most recent version of the payload that is available in the corresponding + /// payload build process at the time of receiving this call. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// OP modifications: + /// - the response type is extended to [`ExecutionPayloadEnvelopeV4`]. + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> TransportResult; + + /// Returns the execution payload bodies by the given hash. + /// + /// See also + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> TransportResult; + + /// Returns the execution payload bodies by the range starting at `start`, containing `count` + /// blocks. + /// + /// WARNING: This method is associated with the BeaconBlocksByRange message in the consensus + /// layer p2p specification, meaning the input should be treated as untrusted or potentially + /// adversarial. + /// + /// Implementers should take care when acting on the input to this method, specifically + /// ensuring that the range is limited properly, and that the range boundaries are computed + /// correctly and without panics. + /// + /// See also + async fn get_payload_bodies_by_range_v1( + &self, + start: u64, + count: u64, + ) -> TransportResult; + + /// Returns the execution client version information. + /// + /// Note: + /// > The `client_version` parameter identifies the consensus client. + /// + /// See also + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> TransportResult>; + + /// Returns the list of Engine API methods supported by the execution layer client software. + /// + /// See also + async fn exchange_capabilities( + &self, + capabilities: Vec, + ) -> TransportResult>; + + /// Signals superchain information to the Engine + /// + /// V1 signals which protocol version is recommended and required. + async fn signal_superchain_v1( + &self, + signal: SuperchainSignal, + ) -> TransportResult; +} + +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +impl OpEngineApi for P +where + N: Network, + T: Transport + Clone, + P: Provider, +{ + async fn new_payload_v2( + &self, + payload: ExecutionPayloadInputV2, + ) -> TransportResult { + self.client().request("engine_newPayloadV2", (payload,)).await + } + + async fn new_payload_v3( + &self, + payload: ExecutionPayloadV3, + parent_beacon_block_root: B256, + ) -> TransportResult { + // Note: The `versioned_hashes` parameter is always an empty array for OP chains. + let versioned_hashes: Vec = vec![]; + + self.client() + .request("engine_newPayloadV3", (payload, versioned_hashes, parent_beacon_block_root)) + .await + } + + async fn new_payload_v4( + &self, + payload: ExecutionPayloadV4, + parent_beacon_block_root: B256, + ) -> TransportResult { + // Note: The `versioned_hashes` parameter is always an empty array for OP chains. + let versioned_hashes: Vec = vec![]; + + self.client() + .request("engine_newPayloadV4", (payload, versioned_hashes, parent_beacon_block_root)) + .await + } + + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult { + self.client() + .request("engine_forkchoiceUpdatedV2", (fork_choice_state, payload_attributes)) + .await + } + + async fn fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult { + self.client() + .request("engine_forkchoiceUpdatedV3", (fork_choice_state, payload_attributes)) + .await + } + + async fn get_payload_v2( + &self, + payload_id: PayloadId, + ) -> TransportResult { + self.client().request("engine_getPayloadV2", (payload_id,)).await + } + + async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> TransportResult { + self.client().request("engine_getPayloadV3", (payload_id,)).await + } + + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> TransportResult { + self.client().request("engine_getPayloadV4", (payload_id,)).await + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> TransportResult { + self.client().request("engine_getPayloadBodiesByHashV1", (block_hashes,)).await + } + + async fn get_payload_bodies_by_range_v1( + &self, + start: u64, + count: u64, + ) -> TransportResult { + self.client().request("engine_getPayloadBodiesByRangeV1", (start, count)).await + } + + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> TransportResult> { + self.client().request("engine_getClientVersionV1", (client_version,)).await + } + + async fn exchange_capabilities( + &self, + capabilities: Vec, + ) -> TransportResult> { + self.client().request("engine_exchangeCapabilities", (capabilities,)).await + } + + async fn signal_superchain_v1( + &self, + signal: SuperchainSignal, + ) -> TransportResult { + self.client().request("engine_signalSuperchainV1", (signal,)).await + } +} diff --git a/crates/provider/src/ext/mod.rs b/crates/provider/src/ext/mod.rs new file mode 100644 index 00000000..d9888a49 --- /dev/null +++ b/crates/provider/src/ext/mod.rs @@ -0,0 +1,4 @@ +//! Extended APIs for the OP provider module. + +/// Engine API extension. +pub mod engine; diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs new file mode 100644 index 00000000..c8e6bb8b --- /dev/null +++ b/crates/provider/src/lib.rs @@ -0,0 +1,19 @@ +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", + html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" +)] +#![warn( + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + clippy::missing_const_for_fn, + rustdoc::all +)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod ext; diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index 7bf60522..d9021296 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -24,6 +24,7 @@ derive_more = { workspace = true, features = ["display"] } # serde serde = { workspace = true, optional = true } alloy-serde = { workspace = true, optional = true } +cfg-if = { version = "1.0" } [dev-dependencies] serde_json.workspace = true diff --git a/crates/rpc-types-engine/src/lib.rs b/crates/rpc-types-engine/src/lib.rs index a7f124ca..46fca284 100644 --- a/crates/rpc-types-engine/src/lib.rs +++ b/crates/rpc-types-engine/src/lib.rs @@ -36,6 +36,9 @@ pub use payload_v3::OptimismExecutionPayloadEnvelopeV3; mod payload_v4; pub use payload_v4::OptimismExecutionPayloadEnvelopeV4; +mod superchain; +pub use superchain::{ProtocolVersion, SuperchainSignal}; + mod errors; pub use errors::{ToL2BlockRefError, ToSystemConfigError}; diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs new file mode 100644 index 00000000..6129acac --- /dev/null +++ b/crates/rpc-types-engine/src/superchain.rs @@ -0,0 +1,146 @@ +use core::array::TryFromSliceError; + +use alloy_primitives::{B256, B64}; +use cfg_if::cfg_if; + +/// Superchain Signal information. +/// +/// The execution engine SHOULD warn the user when the recommended version is newer than the current +/// version supported by the execution engine. +/// +/// The execution engine SHOULD take safety precautions if it does not meet the required protocol +/// version. This may include halting the engine, with consent of the execution engine operator. +/// +/// See also: +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct SuperchainSignal { + /// The recommended Supercain Protocol Version. + pub recommended: ProtocolVersion, + /// The minimum Supercain Protocol Version required. + pub required: ProtocolVersion, +} + +/// Formatted Superchain Protocol Version. +/// +/// The Protocol Version documents the progression of the total set of canonical OP-Stack +/// specifications. Components of the OP-Stack implement the subset of their respective protocol +/// component domain, up to a given Protocol Version of the OP-Stack. +/// +/// The Protocol Version **is NOT a hardfork identifier**, but rather indicates software-support for +/// a well-defined set of features introduced in past and future hardforks, not the activation of +/// said hardforks. +/// +/// The Protocol Version is Semver-compatible. It is encoded as a single 32 bytes long +/// . The version must be encoded as 32 bytes of DATA in JSON RPC usage. +/// +/// See also: +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ProtocolVersion { + /// Version-type 0. + V0(ProtocolVersionFormatV0), +} + +cfg_if! { + if #[cfg(feature = "serde")] { + use serde::{de, Serialize, Serializer, Deserialize, Deserializer}; + + impl Serialize for ProtocolVersion { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::V0(value) => { + // ::= + // ::= + // ::= <31 bytes> + let mut bytes = [0u8; 32]; + bytes[0] = 0x00; // this is not necessary, but addded for clarity + bytes[1..].copy_from_slice(&Into::<[u8; 31]>::into(*value)); + + B256::from_slice(&bytes).serialize(serializer) + } + } + } + } + + impl<'de> Deserialize<'de> for ProtocolVersion { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // The version must be encoded as 32 bytes of DATA in JSON RPC usage. + let value = B256::deserialize(deserializer)?; + + // ::= + // ::= + // ::= <31 bytes> + let version_type = value[0]; + let typed_payload = &value[1..]; + + match version_type { + 0 => Ok(Self::V0(ProtocolVersionFormatV0::try_from(typed_payload).map_err(de::Error::custom)?)), + other => Err(de::Error::custom(format!("unsupported protocol version: {}", other))), + } + } + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ProtocolVersionFormatV0 { + pub build: B64, + pub major: u32, + pub minor: u32, + pub patch: u32, + pub pre_release: u32, +} + +impl From for [u8; 31] { + fn from(value: ProtocolVersionFormatV0) -> Self { + // Version-type 0: + // + // + // ::= <7 zeroed bytes> + // ::= <8 bytes> + // ::= + // ::= + // ::= + // ::= + + let mut bytes = [0u8; 31]; + bytes[0..7].copy_from_slice(&[0u8; 7]); + bytes[7..15].copy_from_slice(&value.build.0); + bytes[15..19].copy_from_slice(&value.major.to_be_bytes()); + bytes[19..23].copy_from_slice(&value.minor.to_be_bytes()); + bytes[23..27].copy_from_slice(&value.patch.to_be_bytes()); + bytes[27..31].copy_from_slice(&value.pre_release.to_be_bytes()); + bytes + } +} + +impl TryFrom<&[u8]> for ProtocolVersionFormatV0 { + type Error = TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + // Version-type 0: + // + // + // ::= <7 zeroed bytes> + // ::= <8 bytes> + // ::= + // ::= + // ::= + // ::= + + Ok(Self { + build: B64::from_slice(&value[7..15]), + major: u32::from_be_bytes(value[15..19].try_into()?), + minor: u32::from_be_bytes(value[19..23].try_into()?), + patch: u32::from_be_bytes(value[23..27].try_into()?), + pre_release: u32::from_be_bytes(value[27..31].try_into()?), + }) + } +} From b04fc0d070958dd42046eaab0be822e75fce2a96 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:26:32 +0200 Subject: [PATCH 02/16] chore: rm unused arbitrary feature --- crates/provider/Cargo.toml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index e45e5e60..d761e846 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -22,26 +22,11 @@ alloy-provider.workspace = true alloy-network.workspace = true alloy-transport.workspace = true -# arbitrary -arbitrary = { workspace = true, features = ["derive"], optional = true } - # misc async-trait = "0.1.82" -[dev-dependencies] -alloy-primitives = { workspace = true, features = ["arbitrary"] } -alloy-consensus = { workspace = true, features = ["arbitrary"] } -alloy-rpc-types = { workspace = true, features = ["arbitrary"] } -arbitrary = { workspace = true, features = ["derive"] } -rand.workspace = true - [features] default = ["std"] std = [ "alloy-primitives/std", ] -arbitrary = [ - "std", - "dep:arbitrary", - "alloy-primitives/arbitrary", -] From 3dec9f039c0146fa47cf6776a339e6e964bc2b10 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:27:51 +0200 Subject: [PATCH 03/16] chore: dep --- crates/rpc-types-engine/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index d9021296..d6511c99 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -20,11 +20,11 @@ op-alloy-genesis.workspace = true op-alloy-consensus.workspace = true derive_more = { workspace = true, features = ["display"] } +cfg-if.workspace = true # serde serde = { workspace = true, optional = true } alloy-serde = { workspace = true, optional = true } -cfg-if = { version = "1.0" } [dev-dependencies] serde_json.workspace = true From 022100e76ff5090bd1975a35cfccbaddf70af20f Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:29:22 +0200 Subject: [PATCH 04/16] chore: rm no_std --- crates/provider/Cargo.toml | 6 ------ crates/provider/src/lib.rs | 1 - 2 files changed, 7 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index d761e846..1ec69eaa 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -24,9 +24,3 @@ alloy-transport.workspace = true # misc async-trait = "0.1.82" - -[features] -default = ["std"] -std = [ - "alloy-primitives/std", -] diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index c8e6bb8b..367d6ece 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -14,6 +14,5 @@ #![deny(unused_must_use, rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(not(feature = "std"), no_std)] pub mod ext; From 8ce5a98b1befd569bceee3ad8b389db2eabe5f76 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:32:17 +0200 Subject: [PATCH 05/16] chore: doc err --- crates/provider/src/ext/engine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/provider/src/ext/engine.rs b/crates/provider/src/ext/engine.rs index 1919445d..95439da0 100644 --- a/crates/provider/src/ext/engine.rs +++ b/crates/provider/src/ext/engine.rs @@ -128,7 +128,7 @@ pub trait OpEngineApi: Send + Sync { /// > Provider software MAY stop the corresponding build process after serving this call. /// /// OP modifications: - /// - the response type is extended to [`ExecutionPayloadEnvelopeV4`]. + /// - the response type is extended to [`OptimismExecutionPayloadEnvelopeV4`]. async fn get_payload_v4( &self, payload_id: PayloadId, From 41892f667402c1be50724ff52bd5b96cdb711aa8 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:35:22 +0200 Subject: [PATCH 06/16] chore: doc err --- crates/rpc-types-engine/src/superchain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 6129acac..6a6e3ea1 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -33,7 +33,7 @@ pub struct SuperchainSignal { /// said hardforks. /// /// The Protocol Version is Semver-compatible. It is encoded as a single 32 bytes long -/// . The version must be encoded as 32 bytes of DATA in JSON RPC usage. +/// protocol version. The version must be encoded as 32 bytes of DATA in JSON RPC usage. /// /// See also: #[derive(Copy, Clone, Debug, PartialEq, Eq)] From e1f2dd21f91b34acabd0dba41199ec380e082155 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:12:30 +0200 Subject: [PATCH 07/16] chore: cleanup --- crates/rpc-types-engine/Cargo.toml | 2 +- crates/rpc-types-engine/src/superchain.rs | 170 +++++++++++++--------- 2 files changed, 103 insertions(+), 69 deletions(-) diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index d6511c99..208d5351 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -20,7 +20,7 @@ op-alloy-genesis.workspace = true op-alloy-consensus.workspace = true derive_more = { workspace = true, features = ["display"] } -cfg-if.workspace = true +thiserror.workspace = true # serde serde = { workspace = true, optional = true } diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 6a6e3ea1..bb36f4fc 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -1,7 +1,6 @@ use core::array::TryFromSliceError; use alloy_primitives::{B256, B64}; -use cfg_if::cfg_if; /// Superchain Signal information. /// @@ -37,58 +36,77 @@ pub struct SuperchainSignal { /// /// See also: #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum ProtocolVersion { /// Version-type 0. V0(ProtocolVersionFormatV0), } -cfg_if! { - if #[cfg(feature = "serde")] { - use serde::{de, Serialize, Serializer, Deserialize, Deserializer}; - - impl Serialize for ProtocolVersion { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Self::V0(value) => { - // ::= - // ::= - // ::= <31 bytes> - let mut bytes = [0u8; 32]; - bytes[0] = 0x00; // this is not necessary, but addded for clarity - bytes[1..].copy_from_slice(&Into::<[u8; 31]>::into(*value)); - - B256::from_slice(&bytes).serialize(serializer) - } - } - } - } +#[derive(Copy, Clone, Debug, thiserror::Error)] +pub enum ProtocolVersionError { + #[error("Unsupported version: {0}")] + UnsupportedVersion(u8), + #[error("Invalid version format length. Got {0}, expected 31")] + InvalidLength(usize), + #[error("Invalid version format encoding")] + FromSlice(#[from] TryFromSliceError), +} - impl<'de> Deserialize<'de> for ProtocolVersion { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - // The version must be encoded as 32 bytes of DATA in JSON RPC usage. - let value = B256::deserialize(deserializer)?; +impl From for B256 { + fn from(value: ProtocolVersion) -> B256 { + let mut bytes = [0u8; 32]; + match value { + ProtocolVersion::V0(value) => { // ::= // ::= // ::= <31 bytes> - let version_type = value[0]; - let typed_payload = &value[1..]; - - match version_type { - 0 => Ok(Self::V0(ProtocolVersionFormatV0::try_from(typed_payload).map_err(de::Error::custom)?)), - other => Err(de::Error::custom(format!("unsupported protocol version: {}", other))), - } + bytes[0] = 0x00; // this is not necessary, but addded for clarity + bytes[1..].copy_from_slice(&value.into_slice()); + B256::from_slice(&bytes) } } } } +impl TryFrom for ProtocolVersion { + type Error = ProtocolVersionError; + + fn try_from(value: B256) -> Result { + // ::= + // ::= + // ::= <31 bytes> + let version_type = value[0]; + let typed_payload = &value[1..]; + + match version_type { + 0 => Ok(Self::V0(ProtocolVersionFormatV0::try_from_slice(typed_payload)?)), + other => Err(ProtocolVersionError::UnsupportedVersion(other)), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for ProtocolVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + B256::from(*self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for ProtocolVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = B256::deserialize(deserializer)?; + Self::try_from(value).map_err(serde::de::Error::custom) + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ProtocolVersionFormatV0 { pub build: B64, @@ -98,42 +116,58 @@ pub struct ProtocolVersionFormatV0 { pub pre_release: u32, } -impl From for [u8; 31] { - fn from(value: ProtocolVersionFormatV0) -> Self { - // Version-type 0: - // - // - // ::= <7 zeroed bytes> - // ::= <8 bytes> - // ::= - // ::= - // ::= - // ::= +impl std::fmt::Display for ProtocolVersionFormatV0 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{build}.{major}.{minor}.{patch}-{pre_release}", + build = self.build, + major = self.major, + minor = self.minor, + patch = self.patch, + pre_release = self.pre_release + ) + } +} +impl ProtocolVersionFormatV0 { + /// Version-type 0 byte encoding: + /// + /// ```text + /// + /// ::= <7 zeroed bytes> + /// ::= <8 bytes> + /// ::= + /// ::= + /// ::= + /// ::= + /// ``` + pub fn into_slice(self) -> [u8; 31] { let mut bytes = [0u8; 31]; bytes[0..7].copy_from_slice(&[0u8; 7]); - bytes[7..15].copy_from_slice(&value.build.0); - bytes[15..19].copy_from_slice(&value.major.to_be_bytes()); - bytes[19..23].copy_from_slice(&value.minor.to_be_bytes()); - bytes[23..27].copy_from_slice(&value.patch.to_be_bytes()); - bytes[27..31].copy_from_slice(&value.pre_release.to_be_bytes()); + bytes[7..15].copy_from_slice(&self.build.0); + bytes[15..19].copy_from_slice(&self.major.to_be_bytes()); + bytes[19..23].copy_from_slice(&self.minor.to_be_bytes()); + bytes[23..27].copy_from_slice(&self.patch.to_be_bytes()); + bytes[27..31].copy_from_slice(&self.pre_release.to_be_bytes()); bytes } -} -impl TryFrom<&[u8]> for ProtocolVersionFormatV0 { - type Error = TryFromSliceError; - - fn try_from(value: &[u8]) -> Result { - // Version-type 0: - // - // - // ::= <7 zeroed bytes> - // ::= <8 bytes> - // ::= - // ::= - // ::= - // ::= + /// Version-type 0 byte encoding: + /// + /// ```text + /// + /// ::= <7 zeroed bytes> + /// ::= <8 bytes> + /// ::= + /// ::= + /// ::= + /// ::= + /// ``` + fn try_from_slice(value: &[u8]) -> Result { + if value.len() != 31 { + return Err(ProtocolVersionError::InvalidLength(value.len())); + } Ok(Self { build: B64::from_slice(&value[7..15]), From d4ffa4c2e6c0d52a396bd4b237be060552a6969a Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:21:36 +0200 Subject: [PATCH 08/16] chore: ci fix --- crates/rpc-types-engine/Cargo.toml | 3 ++- crates/rpc-types-engine/src/superchain.rs | 31 +++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index 208d5351..e7707e6c 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -20,7 +20,7 @@ op-alloy-genesis.workspace = true op-alloy-consensus.workspace = true derive_more = { workspace = true, features = ["display"] } -thiserror.workspace = true +thiserror = { workspace = true, optional = true } # serde serde = { workspace = true, optional = true } @@ -32,6 +32,7 @@ serde_json.workspace = true [features] default = ["std", "serde"] std = [ + "dep:thiserror", "alloy-primitives/std", "alloy-rpc-types-engine/std", "op-alloy-protocol/std", diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index bb36f4fc..9a7d99ec 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -1,6 +1,4 @@ -use core::array::TryFromSliceError; - -use alloy_primitives::{B256, B64}; +use alloy_primitives::B64; /// Superchain Signal information. /// @@ -42,6 +40,7 @@ pub enum ProtocolVersion { V0(ProtocolVersionFormatV0), } +#[cfg(feature = "std")] #[derive(Copy, Clone, Debug, thiserror::Error)] pub enum ProtocolVersionError { #[error("Unsupported version: {0}")] @@ -49,30 +48,32 @@ pub enum ProtocolVersionError { #[error("Invalid version format length. Got {0}, expected 31")] InvalidLength(usize), #[error("Invalid version format encoding")] - FromSlice(#[from] TryFromSliceError), + FromSlice(#[from] core::array::TryFromSliceError), } -impl From for B256 { - fn from(value: ProtocolVersion) -> B256 { +#[cfg(feature = "std")] +impl From for alloy_primitives::B256 { + fn from(value: ProtocolVersion) -> alloy_primitives::B256 { let mut bytes = [0u8; 32]; + // ::= + // ::= + // ::= <31 bytes> match value { ProtocolVersion::V0(value) => { - // ::= - // ::= - // ::= <31 bytes> bytes[0] = 0x00; // this is not necessary, but addded for clarity bytes[1..].copy_from_slice(&value.into_slice()); - B256::from_slice(&bytes) + alloy_primitives::B256::from_slice(&bytes) } } } } -impl TryFrom for ProtocolVersion { +#[cfg(feature = "std")] +impl TryFrom for ProtocolVersion { type Error = ProtocolVersionError; - fn try_from(value: B256) -> Result { + fn try_from(value: alloy_primitives::B256) -> Result { // ::= // ::= // ::= <31 bytes> @@ -92,7 +93,7 @@ impl serde::Serialize for ProtocolVersion { where S: serde::Serializer, { - B256::from(*self).serialize(serializer) + alloy_primitives::B256::from(*self).serialize(serializer) } } @@ -102,7 +103,7 @@ impl<'de> serde::Deserialize<'de> for ProtocolVersion { where D: serde::Deserializer<'de>, { - let value = B256::deserialize(deserializer)?; + let value = alloy_primitives::B256::deserialize(deserializer)?; Self::try_from(value).map_err(serde::de::Error::custom) } } @@ -116,6 +117,7 @@ pub struct ProtocolVersionFormatV0 { pub pre_release: u32, } +#[cfg(feature = "std")] impl std::fmt::Display for ProtocolVersionFormatV0 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -130,6 +132,7 @@ impl std::fmt::Display for ProtocolVersionFormatV0 { } } +#[cfg(feature = "std")] impl ProtocolVersionFormatV0 { /// Version-type 0 byte encoding: /// From 1aa9f33d01f04a7d3137ee701ad96cff3e96bd06 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:28:48 +0200 Subject: [PATCH 09/16] chore: rm from b256 --- crates/rpc-types-engine/src/superchain.rs | 51 ++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 9a7d99ec..89e940fa 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -45,43 +45,46 @@ pub enum ProtocolVersion { pub enum ProtocolVersionError { #[error("Unsupported version: {0}")] UnsupportedVersion(u8), - #[error("Invalid version format length. Got {0}, expected 31")] - InvalidLength(usize), + #[error("Invalid version format length. Got {0}, expected {1}")] + InvalidLength(usize, usize), #[error("Invalid version format encoding")] FromSlice(#[from] core::array::TryFromSliceError), } #[cfg(feature = "std")] -impl From for alloy_primitives::B256 { - fn from(value: ProtocolVersion) -> alloy_primitives::B256 { +impl ProtocolVersion { + /// Version-type 0 byte encoding: + /// + /// ```text + /// ::= + /// ::= + /// ::= <31 bytes> + /// ``` + pub fn encode(&self) -> alloy_primitives::B256 { let mut bytes = [0u8; 32]; - // ::= - // ::= - // ::= <31 bytes> - match value { + match self { ProtocolVersion::V0(value) => { bytes[0] = 0x00; // this is not necessary, but addded for clarity - bytes[1..].copy_from_slice(&value.into_slice()); + bytes[1..].copy_from_slice(&value.encode()); alloy_primitives::B256::from_slice(&bytes) } } } -} -#[cfg(feature = "std")] -impl TryFrom for ProtocolVersion { - type Error = ProtocolVersionError; - - fn try_from(value: alloy_primitives::B256) -> Result { - // ::= - // ::= - // ::= <31 bytes> + /// Version-type 0 byte decoding: + /// + /// ```text + /// ::= + /// ::= + /// ::= <31 bytes> + /// ``` + pub fn decode(value: alloy_primitives::B256) -> Result { let version_type = value[0]; let typed_payload = &value[1..]; match version_type { - 0 => Ok(Self::V0(ProtocolVersionFormatV0::try_from_slice(typed_payload)?)), + 0 => Ok(Self::V0(ProtocolVersionFormatV0::decode(typed_payload)?)), other => Err(ProtocolVersionError::UnsupportedVersion(other)), } } @@ -93,7 +96,7 @@ impl serde::Serialize for ProtocolVersion { where S: serde::Serializer, { - alloy_primitives::B256::from(*self).serialize(serializer) + self.encode().serialize(serializer) } } @@ -104,7 +107,7 @@ impl<'de> serde::Deserialize<'de> for ProtocolVersion { D: serde::Deserializer<'de>, { let value = alloy_primitives::B256::deserialize(deserializer)?; - Self::try_from(value).map_err(serde::de::Error::custom) + ProtocolVersion::decode(value).map_err(serde::de::Error::custom) } } @@ -145,7 +148,7 @@ impl ProtocolVersionFormatV0 { /// ::= /// ::= /// ``` - pub fn into_slice(self) -> [u8; 31] { + pub fn encode(&self) -> [u8; 31] { let mut bytes = [0u8; 31]; bytes[0..7].copy_from_slice(&[0u8; 7]); bytes[7..15].copy_from_slice(&self.build.0); @@ -167,9 +170,9 @@ impl ProtocolVersionFormatV0 { /// ::= /// ::= /// ``` - fn try_from_slice(value: &[u8]) -> Result { + fn decode(value: &[u8]) -> Result { if value.len() != 31 { - return Err(ProtocolVersionError::InvalidLength(value.len())); + return Err(ProtocolVersionError::InvalidLength(value.len(), 31)); } Ok(Self { From f86a01b74188f22d52116d4aeac6259008488032 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:43:30 +0200 Subject: [PATCH 10/16] feat: derive_more error for no_std support --- crates/rpc-types-engine/Cargo.toml | 2 -- crates/rpc-types-engine/src/superchain.rs | 29 ++++++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index e7707e6c..7bf60522 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -20,7 +20,6 @@ op-alloy-genesis.workspace = true op-alloy-consensus.workspace = true derive_more = { workspace = true, features = ["display"] } -thiserror = { workspace = true, optional = true } # serde serde = { workspace = true, optional = true } @@ -32,7 +31,6 @@ serde_json.workspace = true [features] default = ["std", "serde"] std = [ - "dep:thiserror", "alloy-primitives/std", "alloy-rpc-types-engine/std", "op-alloy-protocol/std", diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 89e940fa..6ab59d51 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -1,4 +1,7 @@ -use alloy_primitives::B64; +use core::array::TryFromSliceError; + +use alloy_primitives::{B256, B64}; +use derive_more::derive::{Display, From}; /// Superchain Signal information. /// @@ -40,18 +43,17 @@ pub enum ProtocolVersion { V0(ProtocolVersionFormatV0), } -#[cfg(feature = "std")] -#[derive(Copy, Clone, Debug, thiserror::Error)] +#[derive(Copy, Clone, Debug, Display, From)] pub enum ProtocolVersionError { - #[error("Unsupported version: {0}")] + #[display("Unsupported version: {}", _0)] UnsupportedVersion(u8), - #[error("Invalid version format length. Got {0}, expected {1}")] - InvalidLength(usize, usize), - #[error("Invalid version format encoding")] - FromSlice(#[from] core::array::TryFromSliceError), + #[display("Invalid length: got {}, expected {}", got, expected)] + InvalidLength { got: usize, expected: usize }, + #[display("Failed to convert slice to array")] + #[from(TryFromSliceError)] + TryFromSlice, } -#[cfg(feature = "std")] impl ProtocolVersion { /// Version-type 0 byte encoding: /// @@ -60,14 +62,14 @@ impl ProtocolVersion { /// ::= /// ::= <31 bytes> /// ``` - pub fn encode(&self) -> alloy_primitives::B256 { + pub fn encode(&self) -> B256 { let mut bytes = [0u8; 32]; match self { ProtocolVersion::V0(value) => { bytes[0] = 0x00; // this is not necessary, but addded for clarity bytes[1..].copy_from_slice(&value.encode()); - alloy_primitives::B256::from_slice(&bytes) + B256::from_slice(&bytes) } } } @@ -79,7 +81,7 @@ impl ProtocolVersion { /// ::= /// ::= <31 bytes> /// ``` - pub fn decode(value: alloy_primitives::B256) -> Result { + pub fn decode(value: B256) -> Result { let version_type = value[0]; let typed_payload = &value[1..]; @@ -135,7 +137,6 @@ impl std::fmt::Display for ProtocolVersionFormatV0 { } } -#[cfg(feature = "std")] impl ProtocolVersionFormatV0 { /// Version-type 0 byte encoding: /// @@ -172,7 +173,7 @@ impl ProtocolVersionFormatV0 { /// ``` fn decode(value: &[u8]) -> Result { if value.len() != 31 { - return Err(ProtocolVersionError::InvalidLength(value.len(), 31)); + return Err(ProtocolVersionError::InvalidLength { got: value.len(), expected: 31 }); } Ok(Self { From 3a72e3ee254b611d628c11ac321644d439d97a9c Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 19:00:26 +0200 Subject: [PATCH 11/16] chore: fix std impl, add pub methods --- crates/rpc-types-engine/src/superchain.rs | 60 +++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 6ab59d51..59da14f0 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -90,6 +90,55 @@ impl ProtocolVersion { other => Err(ProtocolVersionError::UnsupportedVersion(other)), } } + + /// Returns the inner value of the ProtocolVersion enum + pub const fn inner(&self) -> ProtocolVersionFormatV0 { + match self { + ProtocolVersion::V0(value) => *value, + } + } + + /// Returns the inner value of the ProtocolVersion enum if it is V0, otherwise None + pub const fn as_v0(&self) -> Option { + match self { + ProtocolVersion::V0(value) => Some(*value), + } + } + + /// Differentiates forks and custom-builds of standard protocol + pub const fn build(&self) -> B64 { + match self { + ProtocolVersion::V0(value) => value.build, + } + } + + /// Incompatible API changes + pub const fn major(&self) -> u32 { + match self { + ProtocolVersion::V0(value) => value.major, + } + } + + /// Identifies additional functionality in backwards compatible manner + pub const fn minor(&self) -> u32 { + match self { + ProtocolVersion::V0(value) => value.minor, + } + } + + /// Identifies backward-compatible bug-fixes + pub const fn patch(&self) -> u32 { + match self { + ProtocolVersion::V0(value) => value.patch, + } + } + + /// Identifies unstable versions that may not satisfy the above + pub const fn pre_release(&self) -> u32 { + match self { + ProtocolVersion::V0(value) => value.pre_release, + } + } } #[cfg(feature = "serde")] @@ -115,10 +164,15 @@ impl<'de> serde::Deserialize<'de> for ProtocolVersion { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ProtocolVersionFormatV0 { + /// Differentiates forks and custom-builds of standard protocol pub build: B64, + /// Incompatible API changes pub major: u32, + /// Identifies additional functionality in backwards compatible manner pub minor: u32, + /// Identifies backward-compatible bug-fixes pub patch: u32, + /// Identifies unstable versions that may not satisfy the above pub pre_release: u32, } @@ -127,12 +181,12 @@ impl std::fmt::Display for ProtocolVersionFormatV0 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{build}.{major}.{minor}.{patch}-{pre_release}", - build = self.build, + "v{major}.{minor}.{patch}-{pre_release}+{build}", major = self.major, minor = self.minor, patch = self.patch, - pre_release = self.pre_release + pre_release = self.pre_release, + build = self.build, ) } } From b9e4c14816cc378a1a94ba6ba44f55395ed9507a Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 19:23:29 +0200 Subject: [PATCH 12/16] chore: switch to core::fmt, add decoding tests --- crates/rpc-types-engine/src/superchain.rs | 111 +++++++++++++++++++--- 1 file changed, 99 insertions(+), 12 deletions(-) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 59da14f0..62a46830 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -43,6 +43,14 @@ pub enum ProtocolVersion { V0(ProtocolVersionFormatV0), } +impl core::fmt::Display for ProtocolVersion { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ProtocolVersion::V0(value) => write!(f, "{}", value), + } + } +} + #[derive(Copy, Clone, Debug, Display, From)] pub enum ProtocolVersionError { #[display("Unsupported version: {}", _0)] @@ -139,6 +147,13 @@ impl ProtocolVersion { ProtocolVersion::V0(value) => value.pre_release, } } + + /// Returns a human-readable string representation of the ProtocolVersion + pub fn display(&self) -> String { + match self { + ProtocolVersion::V0(value) => format!("{}", value), + } + } } #[cfg(feature = "serde")] @@ -176,18 +191,18 @@ pub struct ProtocolVersionFormatV0 { pub pre_release: u32, } -#[cfg(feature = "std")] -impl std::fmt::Display for ProtocolVersionFormatV0 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "v{major}.{minor}.{patch}-{pre_release}+{build}", - major = self.major, - minor = self.minor, - patch = self.patch, - pre_release = self.pre_release, - build = self.build, - ) +impl core::fmt::Display for ProtocolVersionFormatV0 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let build_tag = if self.build.0.iter().any(|&byte| byte != 0) { + format!("+{}", self.build) + } else { + String::new() + }; + + let pre_release_tag = + if self.pre_release != 0 { format!("-{}", self.pre_release) } else { String::new() }; + + write!(f, "v{}.{}.{}{}{}", self.major, self.minor, self.patch, pre_release_tag, build_tag) } } @@ -239,3 +254,75 @@ impl ProtocolVersionFormatV0 { }) } } + +#[cfg(test)] +mod tests { + use alloy_primitives::b256; + + use super::*; + + #[test] + fn test_protocol_version_encode_decode() { + let test_cases = vec![ + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), + major: 42, + minor: 0, + patch: 2, + pre_release: 0, + }), + "v42.0.2+0x6162010000000000", + b256!("000000000000000061620100000000000000002a000000000000000200000000"), + ), + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), + major: 42, + minor: 0, + patch: 2, + pre_release: 1, + }), + "v42.0.2-1+0x6162010000000000", + b256!("000000000000000061620100000000000000002a000000000000000200000001"), + ), + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]), + major: 42, + minor: 0, + patch: 2, + pre_release: 0, + }), + "v42.0.2+0x0102030405060708", + b256!("000000000000000001020304050607080000002a000000000000000200000000"), + ), + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + major: 0, + minor: 100, + patch: 2, + pre_release: 0, + }), + "v0.100.2", + b256!("0000000000000000000000000000000000000000000000640000000200000000"), + ), + ]; + + for (decoded_exp, formatted_exp, encoded_exp) in test_cases { + encode_decode_v0(encoded_exp, formatted_exp, decoded_exp); + } + } + + fn encode_decode_v0(encoded_exp: B256, formatted_exp: &str, decoded_exp: ProtocolVersion) { + let decoded = ProtocolVersion::decode(encoded_exp).unwrap(); + assert_eq!(decoded, decoded_exp); + + let encoded = decoded.encode(); + assert_eq!(encoded, encoded_exp); + + let formatted = decoded.display(); + assert_eq!(formatted, formatted_exp); + } +} From c8adfbe65a63062337c982a4694d2f0fbea72c17 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sat, 21 Sep 2024 19:27:47 +0200 Subject: [PATCH 13/16] chore: no_std fix --- crates/rpc-types-engine/src/superchain.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 62a46830..4286cb2d 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -1,3 +1,4 @@ +use alloc::{format, string::String}; use core::array::TryFromSliceError; use alloy_primitives::{B256, B64}; From ffc6c1f686e61cb4f0b1cf939557ab056336ab25 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sun, 22 Sep 2024 11:55:23 +0200 Subject: [PATCH 14/16] feat: added human readable build decoding --- crates/rpc-types-engine/src/superchain.rs | 71 ++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 4286cb2d..6a07ffa0 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -178,6 +178,18 @@ impl<'de> serde::Deserialize<'de> for ProtocolVersion { } } +/// The Protocol Version V0 format. +/// Encoded as 31 bytes with the following structure: +/// +/// ```text +/// +/// ::= <7 zeroed bytes> +/// ::= <8 bytes> +/// ::= +/// ::= +/// ::= +/// ::= +/// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ProtocolVersionFormatV0 { /// Differentiates forks and custom-builds of standard protocol @@ -195,7 +207,12 @@ pub struct ProtocolVersionFormatV0 { impl core::fmt::Display for ProtocolVersionFormatV0 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let build_tag = if self.build.0.iter().any(|&byte| byte != 0) { - format!("+{}", self.build) + if is_human_readable_build_tag(self.build) { + let full = format!("+{}", String::from_utf8_lossy(&self.build.0)); + full.trim_end_matches('\0').to_string() + } else { + format!("+{}", self.build) + } } else { String::new() }; @@ -256,6 +273,25 @@ impl ProtocolVersionFormatV0 { } } +/// Returns true if the build tag is human-readable, false otherwise. +fn is_human_readable_build_tag(build: B64) -> bool { + for (i, &c) in build.iter().enumerate() { + if c == 0 { + // Trailing zeros are allowed + if build[i..].iter().any(|&d| d != 0) { + return false; + } + return true; + } + + // following semver.org advertised regex, alphanumeric with '-' and '.', except leading '.'. + if !(c.is_ascii_alphanumeric() || c == b'-' || (c == b'.' && i > 0)) { + return false; + } + } + true +} + #[cfg(test)] mod tests { use alloy_primitives::b256; @@ -309,6 +345,39 @@ mod tests { "v0.100.2", b256!("0000000000000000000000000000000000000000000000640000000200000000"), ), + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(&[b'O', b'P', b'-', b'm', b'o', b'd', 0x00, 0x00]), + major: 42, + minor: 0, + patch: 2, + pre_release: 1, + }), + "v42.0.2-1+OP-mod", + b256!("00000000000000004f502d6d6f6400000000002a000000000000000200000001"), + ), + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(&[b'a', b'b', 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), + major: 42, + minor: 0, + patch: 2, + pre_release: 0, + }), + "v42.0.2+0x6162010000000000", // do not render invalid alpha numeric + b256!("000000000000000061620100000000000000002a000000000000000200000000"), + ), + ( + ProtocolVersion::V0(ProtocolVersionFormatV0 { + build: B64::from_slice(b"beta.123"), + major: 1, + minor: 0, + patch: 0, + pre_release: 0, + }), + "v1.0.0+beta.123", + b256!("0000000000000000626574612e31323300000001000000000000000000000000"), + ), ]; for (decoded_exp, formatted_exp, encoded_exp) in test_cases { From 46aa9241a0458415bc6c11c631ef1f5634a2e4fe Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Sun, 22 Sep 2024 11:58:38 +0200 Subject: [PATCH 15/16] chore: no_std fix --- crates/rpc-types-engine/src/superchain.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/rpc-types-engine/src/superchain.rs b/crates/rpc-types-engine/src/superchain.rs index 6a07ffa0..0af437a9 100644 --- a/crates/rpc-types-engine/src/superchain.rs +++ b/crates/rpc-types-engine/src/superchain.rs @@ -1,4 +1,7 @@ -use alloc::{format, string::String}; +use alloc::{ + format, + string::{String, ToString}, +}; use core::array::TryFromSliceError; use alloy_primitives::{B256, B64}; From 2161b32f73fd0cae0408310b6c3d76e4fa89d36a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 22 Sep 2024 15:44:23 +0200 Subject: [PATCH 16/16] exclude alloy provider for wasm --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18eeab43..1174edc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: cache-on-failure: true - name: cargo hack run: | - cargo hack build --workspace --target wasm32-unknown-unknown --exclude op-alloy-network --exclude op-alloy-rpc-types --exclude op-alloy-rpc-jsonrpsee + cargo hack build --workspace --target wasm32-unknown-unknown --exclude op-alloy-network --exclude op-alloy-rpc-types --exclude op-alloy-rpc-jsonrpsee --exclude op-alloy-provider wasm-wasi: runs-on: ubuntu-latest @@ -94,7 +94,7 @@ jobs: cache-on-failure: true - name: cargo hack run: | - cargo hack build --workspace --target wasm32-wasi --exclude op-alloy-network --exclude op-alloy-rpc-types --exclude op-alloy-rpc-jsonrpsee + cargo hack build --workspace --target wasm32-wasi --exclude op-alloy-network --exclude op-alloy-rpc-types --exclude op-alloy-rpc-jsonrpsee --exclude op-alloy-provider no-std: runs-on: ubuntu-latest