diff --git a/crates/consensus/src/receipt/envelope.rs b/crates/consensus/src/receipt/envelope.rs index ddc19f49..c2831b06 100644 --- a/crates/consensus/src/receipt/envelope.rs +++ b/crates/consensus/src/receipt/envelope.rs @@ -317,7 +317,7 @@ mod tests { use alloy_rlp::Encodable; #[cfg(not(feature = "std"))] - use alloc::{vec, vec::Vec}; + use alloc::vec; // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 #[test] diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml index 7aa577b1..ad8f1802 100644 --- a/crates/protocol/Cargo.toml +++ b/crates/protocol/Cargo.toml @@ -26,6 +26,7 @@ alloy-eips.workspace = true alloy-consensus.workspace = true # Misc +brotli.workspace = true tracing.workspace = true derive_more.workspace = true async-trait.workspace = true @@ -62,5 +63,5 @@ test-utils = [ "dep:tracing-subscriber", ] arbitrary = ["std", "dep:arbitrary", "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "alloy-primitives/rand"] -std = ["op-alloy-consensus/std", "op-alloy-genesis/std"] +std = ["op-alloy-consensus/std", "op-alloy-genesis/std", "brotli/std"] serde = ["dep:serde", "dep:alloy-serde", "op-alloy-consensus/serde", "op-alloy-genesis/serde"] diff --git a/crates/protocol/examples/batch_to_frames.rs b/crates/protocol/examples/batch_to_frames.rs index e9e98491..c97a0803 100644 --- a/crates/protocol/examples/batch_to_frames.rs +++ b/crates/protocol/examples/batch_to_frames.rs @@ -12,15 +12,12 @@ //! Finally, once [Frame]s are built from the [ChannelOut], they are encoded and ready //! to be batch-submitted to the data availability layer. -use alloy_consensus::{SignableTransaction, TxEip1559}; -use alloy_eips::eip2718::{Decodable2718, Encodable2718}; -use alloy_primitives::{hex, Address, BlockHash, Bytes, PrimitiveSignature, U256}; -use brotli::enc::{BrotliCompress, BrotliEncoderParams}; -use op_alloy_consensus::OpTxEnvelope; -use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{Batch, ChannelId, ChannelOut, SingleBatch, CHANNEL_ID_LENGTH}; - +#[cfg(feature = "std")] fn main() { + use alloy_primitives::BlockHash; + use op_alloy_genesis::RollupConfig; + use op_alloy_protocol::{Batch, ChannelId, ChannelOut, SingleBatch}; + // Use the example transaction let transactions = example_transactions(); @@ -29,30 +26,16 @@ fn main() { let epoch_num = 1; let epoch_hash = BlockHash::ZERO; let timestamp = 1; - let single_batch = SingleBatch { parent_hash, epoch_num, epoch_hash, timestamp, transactions }; let batch = Batch::Single(single_batch); - // Encode the batch. - let mut encoded = Vec::new(); - batch.encode(&mut encoded).unwrap(); - let config = RollupConfig::default(); - let decoded = Batch::decode(&mut encoded.as_slice(), &config).unwrap(); - assert_eq!(batch, decoded); - println!("Encoded Batch: {}", hex::encode(&encoded)); - - // Compress the encoded batch. - let compressed = compress_brotli(&encoded); - let expected = hex!("1b1301f82f0f6c3734f4821cd090ef3979d71a98e7e483b1dccdd525024c0ef16f425c7b4976a7acc0c94a0514b72c096d4dcc52f0b22dae193c70c86d0790a304a08152c8250031d091063ea0b00d00005082edde7ccf05bded2004462b5e80e1c42cd08e307f5baac723b22864cc6cd01ddde84efc7c018d7ada56c2fa8e3c5bedd494c3a7a884439d5771afcecaf196cb38"); - assert_eq!(compressed, expected); - println!("Brotli-compressed batch: {}", hex::encode(&compressed)); - // Create a new channel. - let id = random_channel_id(); + let id = ChannelId::default(); + let config = RollupConfig::default(); let mut channel_out = ChannelOut::new(id, &config); // Add the compressed batch to the `ChannelOut`. - channel_out.add_raw_compressed_batch(compressed.into()); + channel_out.add_batch(batch).unwrap(); // Output frames while channel_out.ready_bytes() > 0 { @@ -64,24 +47,16 @@ fn main() { } assert!(channel_out.closed); + println!("Successfully encoded Batch to frames"); } -/// Creates a random [ChannelId]. -pub fn random_channel_id() -> ChannelId { - let mut id = [0; CHANNEL_ID_LENGTH]; - id.iter_mut().for_each(|b| *b = rand::random()); - id -} - -/// Compresses the given bytes data using the Brotli compressor implemented -/// in the [`brotli`](https://crates.io/crates/brotli) crate. -pub fn compress_brotli(mut input: &[u8]) -> Vec { - let mut output = vec![]; - BrotliCompress(&mut input, &mut output, &BrotliEncoderParams::default()).expect("succeeds"); - output -} +#[cfg(feature = "std")] +fn example_transactions() -> Vec { + use alloy_consensus::{SignableTransaction, TxEip1559}; + use alloy_eips::eip2718::{Decodable2718, Encodable2718}; + use alloy_primitives::{Address, PrimitiveSignature, U256}; + use op_alloy_consensus::OpTxEnvelope; -fn example_transactions() -> Vec { let mut transactions = Vec::new(); // First Transaction in the batch. @@ -128,3 +103,8 @@ fn example_transactions() -> Vec { transactions } + +#[cfg(not(feature = "std"))] +fn main() { + /* not implemented for no_std */ +} diff --git a/crates/protocol/src/channel.rs b/crates/protocol/src/channel.rs index 37616dae..d9056f4d 100644 --- a/crates/protocol/src/channel.rs +++ b/crates/protocol/src/channel.rs @@ -2,10 +2,9 @@ use alloc::{vec, vec::Vec}; use alloy_primitives::{map::HashMap, Bytes}; -use alloy_rlp::Encodable; use op_alloy_genesis::RollupConfig; -use crate::{block::BlockInfo, frame::Frame, SingleBatch}; +use crate::{block::BlockInfo, frame::Frame}; /// The frame overhead. const FRAME_V0_OVERHEAD: usize = 23; @@ -17,7 +16,7 @@ pub const CHANNEL_ID_LENGTH: usize = 16; pub type ChannelId = [u8; CHANNEL_ID_LENGTH]; /// An error returned by the [ChannelOut] when adding single batches. -#[derive(Debug, Clone, Copy, derive_more::Display, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, derive_more::Display)] pub enum ChannelOutError { /// The channel is closed. ChannelClosed, @@ -25,6 +24,10 @@ pub enum ChannelOutError { MaxFrameSizeTooSmall, /// Missing compressed batch data. MissingData, + /// An error from brotli compression. + BrotliCompression, + /// An error encoding the `Batch`. + BatchEncoding, } impl core::error::Error for ChannelOutError {} @@ -54,25 +57,26 @@ impl<'a> ChannelOut<'a> { Self { id, config, rlp_length: 0, frame_number: 0, closed: false, compressed: None } } - /// Accepts the given [SingleBatch] data into the [ChannelOut], compressing it + /// Accepts the given [crate::Batch] data into the [ChannelOut], compressing it /// into frames. - pub fn add_single_batch(&mut self, batch: SingleBatch) -> Result<(), ChannelOutError> { + #[cfg(feature = "std")] + pub fn add_batch(&mut self, batch: crate::Batch) -> Result<(), ChannelOutError> { if self.closed { return Err(ChannelOutError::ChannelClosed); } // Encode the batch. let mut buf = vec![]; - batch.encode(&mut buf); + batch.encode(&mut buf).map_err(|_| ChannelOutError::BatchEncoding)?; // Validate that the RLP length is within the channel's limits. - let max_rlp_bytes_per_channel = self.config.max_rlp_bytes_per_channel(batch.timestamp); + let max_rlp_bytes_per_channel = self.config.max_rlp_bytes_per_channel(batch.timestamp()); if self.rlp_length + buf.len() as u64 > max_rlp_bytes_per_channel { return Err(ChannelOutError::ChannelClosed); } - // Compress the batch. - todo!("compress the batch with a compression algorithm based on the rollup config") + self.compressed = Some(crate::compress_brotli(&buf).into()); + Ok(()) } /// Returns the number of bytes ready to be output to a frame. @@ -81,6 +85,7 @@ impl<'a> ChannelOut<'a> { } /// Accepts the raw compressed batch data into the [ChannelOut]. + #[deprecated] pub fn add_raw_compressed_batch(&mut self, compressed: Bytes) { self.compressed = Some(compressed); } diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 4cf6f311..72e8cb79 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -37,6 +37,8 @@ mod iter; pub use iter::FrameIter; mod utils; +#[cfg(feature = "std")] +pub use utils::compress_brotli; pub use utils::{read_tx_data, starts_with_2718_deposit, to_system_config}; mod channel; diff --git a/crates/protocol/src/utils.rs b/crates/protocol/src/utils.rs index a36e7bf0..733fe09f 100644 --- a/crates/protocol/src/utils.rs +++ b/crates/protocol/src/utils.rs @@ -1,6 +1,6 @@ //! Utility methods used by protocol types. -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use alloy_consensus::TxType; use alloy_primitives::B256; use alloy_rlp::{Buf, Header}; @@ -12,6 +12,16 @@ use crate::{ SpanDecodingError, }; +/// Compresses the given bytes data using the Brotli compressor implemented +/// in the [`brotli`](https://crates.io/crates/brotli) crate. +#[cfg(feature = "std")] +pub fn compress_brotli(mut input: &[u8]) -> Vec { + use brotli::enc::{BrotliCompress, BrotliEncoderParams}; + let mut output = vec![]; + BrotliCompress(&mut input, &mut output, &BrotliEncoderParams::default()).expect("succeeds"); + output +} + /// Returns if the given `value` is a deposit transaction. pub fn starts_with_2718_deposit(value: &B) -> bool where