diff --git a/Cargo.lock b/Cargo.lock index 8980d4e3..940ae824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,7 +2456,6 @@ dependencies = [ "alloy-rlp", "anyhow", "c-kzg", - "hashbrown 0.14.5", "op-alloy-consensus", "op-alloy-protocol", "revm", @@ -2889,7 +2888,7 @@ dependencies = [ [[package]] name = "op-alloy-protocol" version = "0.2.8" -source = "git+https://github.com/alloy-rs/op-alloy?branch=rf/feat/export-frame-consts#5bab5db85315b98ae9ba704300c44883099e70e7" +source = "git+https://github.com/alloy-rs/op-alloy?branch=rf/fix/use-native-u64#ea93a1fd9e46d9ca38919ebbdbd21e448b475898" dependencies = [ "alloy-primitives", "hashbrown 0.14.5", diff --git a/Cargo.toml b/Cargo.toml index d7333c36..081c8508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ codegen-units = 1 lto = "fat" [patch.crates-io] -op-alloy-protocol = { git = "https://github.com/alloy-rs/op-alloy", branch = "rf/feat/export-frame-consts" } +op-alloy-protocol = { git = "https://github.com/alloy-rs/op-alloy", branch = "rf/fix/use-native-u64" } [workspace.dependencies] # Workspace diff --git a/crates/derive/src/batch/mod.rs b/crates/derive/src/batch/mod.rs index fc492ed2..b9128c29 100644 --- a/crates/derive/src/batch/mod.rs +++ b/crates/derive/src/batch/mod.rs @@ -2,7 +2,7 @@ //! [SingleBatch]. use alloy_rlp::{Buf, Decodable}; -use kona_primitives::{block::BlockInfo, L2BlockInfo, RollupConfig}; +use kona_primitives::{BlockInfo, L2BlockInfo, RollupConfig}; use crate::{errors::DecodeError, traits::L2ChainProvider}; diff --git a/crates/derive/src/stages/channel_bank.rs b/crates/derive/src/stages/channel_bank.rs index 96516ecd..845c1c8e 100644 --- a/crates/derive/src/stages/channel_bank.rs +++ b/crates/derive/src/stages/channel_bank.rs @@ -12,7 +12,7 @@ use anyhow::anyhow; use async_trait::async_trait; use core::fmt::Debug; use hashbrown::HashMap; -use kona_primitives::{BlockInfo, Channel, ChannelID, Frame, RollupConfig, SystemConfig}; +use kona_primitives::{BlockInfo, Channel, ChannelId, Frame, RollupConfig, SystemConfig}; use tracing::{trace, warn}; /// Provides frames for the [ChannelBank] stage. @@ -43,9 +43,9 @@ where /// The rollup configuration. cfg: Arc, /// Map of channels by ID. - channels: HashMap, + channels: HashMap, /// Channels in FIFO order. - channel_queue: VecDeque, + channel_queue: VecDeque, /// The previous stage of the derivation pipeline. prev: P, } @@ -192,7 +192,7 @@ where self.channels.remove(&channel_id); self.channel_queue.remove(index); - frame_data.map_err(StageError::Custom) + frame_data.ok_or_else(|| StageError::Custom(anyhow!("Channel data is empty"))) } } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 39b488d6..a72def6c 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -11,7 +11,6 @@ homepage.workspace = true [dependencies] # General anyhow.workspace = true -hashbrown.workspace = true # Alloy alloy-eips.workspace = true diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 9bab57ae..1432878c 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -1,71 +1,14 @@ //! This module contains the various Block types. -use crate::BlockID; use alloc::vec::Vec; use alloy_consensus::{Header, TxEnvelope}; use alloy_eips::eip4895::Withdrawal; -use alloy_primitives::B256; use alloy_rlp::{RlpDecodable, RlpEncodable}; use op_alloy_consensus::OpTxEnvelope; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -/// Block Header Info -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Default)] -pub struct BlockInfo { - /// The block hash - pub hash: B256, - /// The block number - pub number: u64, - /// The parent block hash - pub parent_hash: B256, - /// The block timestamp - pub timestamp: u64, -} - -impl BlockInfo { - /// Instantiates a new [BlockInfo]. - pub fn new(hash: B256, number: u64, parent_hash: B256, timestamp: u64) -> Self { - Self { hash, number, parent_hash, timestamp } - } - - /// Returns the block ID. - pub fn id(&self) -> BlockID { - BlockID { hash: self.hash, number: self.number } - } -} - -impl core::fmt::Display for BlockInfo { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "BlockInfo {{ hash: {}, number: {}, parent_hash: {}, timestamp: {} }}", - self.hash, self.number, self.parent_hash, self.timestamp - ) - } -} - -/// L2 Block Header Info -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)] -pub struct L2BlockInfo { - /// The base [BlockInfo] - pub block_info: BlockInfo, - /// The L1 origin [BlockID] - pub l1_origin: BlockID, - /// The sequence number of the L2 block - pub seq_num: u64, -} - -impl L2BlockInfo { - /// Instantiates a new [L2BlockInfo]. - pub fn new(block_info: BlockInfo, l1_origin: BlockID, seq_num: u64) -> Self { - Self { block_info, l1_origin, seq_num } - } -} - /// The Block Kind /// /// The block kinds are: diff --git a/crates/primitives/src/channel.rs b/crates/primitives/src/channel.rs deleted file mode 100644 index 26b2132c..00000000 --- a/crates/primitives/src/channel.rs +++ /dev/null @@ -1,284 +0,0 @@ -//! This module contains the [Channel] struct. - -use alloc::vec::Vec; -use alloy_primitives::Bytes; -use anyhow::{anyhow, bail, Result}; -use hashbrown::HashMap; - -use crate::{block::BlockInfo, Frame}; - -/// [MAX_RLP_BYTES_PER_CHANNEL] is the maximum amount of bytes that will be read from -/// a channel. This limit is set when decoding the RLP. -pub const MAX_RLP_BYTES_PER_CHANNEL: u64 = 10_000_000; - -/// [FJORD_MAX_RLP_BYTES_PER_CHANNEL] is the maximum amount of bytes that will be read from -/// a channel when the Fjord Hardfork is activated. This limit is set when decoding the RLP. -pub const FJORD_MAX_RLP_BYTES_PER_CHANNEL: u64 = 100_000_000; - -/// [ChannelID] is an opaque identifier for a channel. -pub type ChannelID = [u8; CHANNEL_ID_LENGTH]; - -/// [CHANNEL_ID_LENGTH] is the length of the channel ID. -pub const CHANNEL_ID_LENGTH: usize = 16; - -/// A Channel is a set of batches that are split into at least one, but possibly multiple frames. -/// -/// Frames are allowed to be ingested out of order. -/// Each frame is ingested one by one. Once a frame with `closed` is added to the channel, the -/// channel may mark itself as ready for reading once all intervening frames have been added -#[derive(Debug, Clone, Default)] -pub struct Channel { - /// The unique identifier for this channel - id: ChannelID, - /// The block that the channel is currently open at - open_block: BlockInfo, - /// Estimated memory size, used to drop the channel if we have too much data - estimated_size: usize, - /// True if the last frame has been buffered - closed: bool, - /// The highest frame number that has been ingested - highest_frame_number: u16, - /// The frame number of the frame where `is_last` is true - /// No other frame number may be higher than this - last_frame_number: u16, - /// Store a map of frame number to frame for constant time ordering - inputs: HashMap, - /// The highest L1 inclusion block that a frame was included in - highest_l1_inclusion_block: BlockInfo, -} - -impl Channel { - /// Create a new [Channel] with the given [ChannelID] and [BlockInfo]. - pub fn new(id: ChannelID, open_block: BlockInfo) -> Self { - Self { id, open_block, inputs: HashMap::new(), ..Default::default() } - } - - /// Returns the current [ChannelID] for the channel. - pub fn id(&self) -> ChannelID { - self.id - } - - /// Returns the number of frames ingested. - pub fn len(&self) -> usize { - self.inputs.len() - } - - /// Returns if the channel is empty. - pub fn is_empty(&self) -> bool { - self.inputs.is_empty() - } - - /// Add a frame to the channel. - /// - /// ## Takes - /// - `frame`: The frame to add to the channel - /// - `l1_inclusion_block`: The block that the frame was included in - /// - /// ## Returns - /// - `Ok(()):` If the frame was successfully buffered - /// - `Err(_):` If the frame was invalid - pub fn add_frame(&mut self, frame: Frame, l1_inclusion_block: BlockInfo) -> Result<()> { - // Ensure that the frame ID is equal to the channel ID. - if frame.id != self.id { - bail!("Frame ID does not match channel ID"); - } - if frame.is_last && self.closed { - bail!("Cannot add ending frame to a closed channel. Channel ID: {:?}", self.id); - } - if self.inputs.contains_key(&frame.number) { - bail!("Frame number already exists in channel. Channel ID: {:?}", self.id); - } - if self.closed && frame.number >= self.last_frame_number { - bail!( - "frame number {} is greater than or equal to end frame number {}", - frame.number, - self.last_frame_number - ); - } - - // Guaranteed to succeed at this point. Update the channel state. - if frame.is_last { - self.last_frame_number = frame.number; - self.closed = true; - - // Prune frames with a higher number than the last frame number when we receive a - // closing frame. - if self.last_frame_number < self.highest_frame_number { - self.inputs.retain(|id, frame| { - self.estimated_size -= frame.size(); - *id < self.last_frame_number - }); - self.highest_frame_number = self.last_frame_number; - } - } - - // Update the highest frame number. - if frame.number > self.highest_frame_number { - self.highest_frame_number = frame.number; - } - - if self.highest_l1_inclusion_block.number < l1_inclusion_block.number { - self.highest_l1_inclusion_block = l1_inclusion_block; - } - - self.estimated_size += frame.size(); - self.inputs.insert(frame.number, frame); - Ok(()) - } - - /// Returns the block number of the L1 block that contained the first [Frame] in this channel. - pub fn open_block_number(&self) -> u64 { - self.open_block.number - } - - /// Returns the estimated size of the channel including [Frame] overhead. - pub fn size(&self) -> usize { - self.estimated_size - } - - /// Returns `true` if the channel is ready to be read. - pub fn is_ready(&self) -> bool { - // Must have buffered the last frame before the channel is ready. - if !self.closed { - return false; - } - - // Must have the possibility of contiguous frames. - if self.inputs.len() != (self.last_frame_number + 1) as usize { - return false; - } - - // Check for contiguous frames. - for i in 0..=self.last_frame_number { - if !self.inputs.contains_key(&i) { - return false; - } - } - - true - } - - /// Returns all of the channel's [Frame]s concatenated together. - pub fn frame_data(&self) -> Result { - let mut data = Vec::with_capacity(self.size()); - (0..=self.last_frame_number).try_for_each(|i| { - let frame = self.inputs.get(&i).ok_or_else(|| anyhow!("Frame not found"))?; - data.extend_from_slice(&frame.data); - Ok::<(), anyhow::Error>(()) - })?; - Ok(data.into()) - } -} - -#[cfg(test)] -mod test { - use std::println; - - use super::Channel; - use crate::{block::BlockInfo, Frame}; - use alloc::{ - string::{String, ToString}, - vec, - vec::Vec, - }; - - struct FrameValidityTestCase { - name: String, - frames: Vec, - should_error: Vec, - sizes: Vec, - } - - fn run_frame_validity_test(test_case: FrameValidityTestCase) { - println!("Running test: {}", test_case.name); - - let id = [0xFF; 16]; - let block = BlockInfo::default(); - let mut channel = Channel::new(id, block); - - if test_case.frames.len() != test_case.should_error.len() || - test_case.frames.len() != test_case.sizes.len() - { - panic!("Test case length mismatch"); - } - - for (i, frame) in test_case.frames.iter().enumerate() { - let result = channel.add_frame(frame.clone(), block); - if test_case.should_error[i] { - assert!(result.is_err()); - } else { - assert!(result.is_ok()); - } - assert_eq!(channel.size(), test_case.sizes[i] as usize); - } - } - - #[test] - fn test_frame_validity() { - let id = [0xFF; 16]; - let test_cases = [ - FrameValidityTestCase { - name: "wrong channel".to_string(), - frames: vec![Frame { id: [0xEE; 16], ..Default::default() }], - should_error: vec![true], - sizes: vec![0], - }, - FrameValidityTestCase { - name: "double close".to_string(), - frames: vec![ - Frame { id, is_last: true, number: 2, data: b"four".to_vec() }, - Frame { id, is_last: true, number: 1, ..Default::default() }, - ], - should_error: vec![false, true], - sizes: vec![204, 204], - }, - FrameValidityTestCase { - name: "duplicate frame".to_string(), - frames: vec![ - Frame { id, number: 2, data: b"four".to_vec(), ..Default::default() }, - Frame { id, number: 2, data: b"seven".to_vec(), ..Default::default() }, - ], - should_error: vec![false, true], - sizes: vec![204, 204], - }, - FrameValidityTestCase { - name: "duplicate closing frames".to_string(), - frames: vec![ - Frame { id, number: 2, is_last: true, data: b"four".to_vec() }, - Frame { id, number: 2, is_last: true, data: b"seven".to_vec() }, - ], - should_error: vec![false, true], - sizes: vec![204, 204], - }, - FrameValidityTestCase { - name: "frame past closing".to_string(), - frames: vec![ - Frame { id, number: 2, is_last: true, data: b"four".to_vec() }, - Frame { id, number: 10, data: b"seven".to_vec(), ..Default::default() }, - ], - should_error: vec![false, true], - sizes: vec![204, 204], - }, - FrameValidityTestCase { - name: "prune after close frame".to_string(), - frames: vec![ - Frame { id, number: 10, is_last: false, data: b"seven".to_vec() }, - Frame { id, number: 2, is_last: true, data: b"four".to_vec() }, - ], - should_error: vec![false, false], - sizes: vec![205, 204], - }, - FrameValidityTestCase { - name: "multiple valid frames".to_string(), - frames: vec![ - Frame { id, number: 10, data: b"seven__".to_vec(), ..Default::default() }, - Frame { id, number: 2, data: b"four".to_vec(), ..Default::default() }, - ], - should_error: vec![false, false], - sizes: vec![207, 411], - }, - ]; - - test_cases.into_iter().for_each(run_frame_validity_test); - } -} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 7d9e13ae..543f479e 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -11,7 +11,10 @@ extern crate alloc; pub use op_alloy_consensus::Hardforks; // Use op-alloy-protocol crate. -pub use op_alloy_protocol::{Frame, DERIVATION_VERSION_0, FRAME_OVERHEAD, MAX_FRAME_LEN}; +pub use op_alloy_protocol::{ + BlockInfo, Channel, ChannelId, Frame, L2BlockInfo, CHANNEL_ID_LENGTH, DERIVATION_VERSION_0, + FJORD_MAX_RLP_BYTES_PER_CHANNEL, FRAME_OVERHEAD, MAX_FRAME_LEN, MAX_RLP_BYTES_PER_CHANNEL, +}; // Re-export superchain-primitives. pub use superchain_primitives::*; @@ -34,7 +37,7 @@ pub use alloy_eips::eip4844::{Blob, BYTES_PER_BLOB, VERSIONED_HASH_VERSION_KZG}; pub use alloy_eips::eip4895::Withdrawal; pub mod block; -pub use block::{Block, BlockInfo, BlockKind, L2BlockInfo, OpBlock}; +pub use block::{Block, BlockKind, OpBlock}; pub mod block_info; pub use block_info::{L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoTx}; @@ -65,9 +68,3 @@ pub use sidecar::{ APIVersionResponse, BeaconBlockHeader, BlobSidecar, SignedBeaconBlockHeader, VersionInformation, KZG_COMMITMENT_SIZE, KZG_PROOF_SIZE, }; - -pub mod channel; -pub use channel::{ - Channel, ChannelID, CHANNEL_ID_LENGTH, FJORD_MAX_RLP_BYTES_PER_CHANNEL, - MAX_RLP_BYTES_PER_CHANNEL, -};