diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d4d06330..c668bcf5 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -117,7 +117,7 @@ jobs: with: cache-on-failure: true - name: Run feature check - run: cargo hack check --feature-powerset + run: cargo hack check --feature-powerset --mutually-exclusive-features "zstd,ruzstd" --at-least-one-of "zstd,ruzstd" no_std: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index a01d658d..004affca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11478,6 +11478,15 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" @@ -11676,6 +11685,7 @@ dependencies = [ "bitvec", "derive_more", "eyre", + "ruzstd", "scroll-alloy-consensus", "scroll-l1", "serde_json", @@ -13571,6 +13581,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + [[package]] name = "typenum" version = "1.19.0" diff --git a/crates/codec/Cargo.toml b/crates/codec/Cargo.toml index 581f8f64..d71f892c 100644 --- a/crates/codec/Cargo.toml +++ b/crates/codec/Cargo.toml @@ -19,11 +19,15 @@ bitvec.workspace = true derive_more = { version = "2.0", default-features = false } eyre = { workspace = true, optional = true } thiserror = { version = "2.0", default-features = false } -zstd = "=0.13.3" +zstd = { version = "=0.13.3", optional = true } +ruzstd = { version = "0.8", optional = true } [dev-dependencies] eyre.workspace = true serde_json = "1.0" [features] +default = ["zstd"] test-utils = ["dep:eyre", "scroll-l1/test-utils"] +zstd = ["dep:zstd"] +ruzstd = ["dep:ruzstd"] diff --git a/crates/codec/src/decoding/v2/mod.rs b/crates/codec/src/decoding/v2/mod.rs index 6427b945..5c83c10b 100644 --- a/crates/codec/src/decoding/v2/mod.rs +++ b/crates/codec/src/decoding/v2/mod.rs @@ -35,7 +35,7 @@ pub fn decode_v2(calldata: &[u8], blob: &[u8]) -> Result { // get blob iterator and collect, skipping unused bytes. let compressed_heap_blob = BlobSliceIter::from_blob_slice(blob).copied().collect::>(); - let uncompressed_heap_blob = decompress_blob_data(&compressed_heap_blob); + let uncompressed_heap_blob = decompress_blob_data(&compressed_heap_blob)?; let buf = &mut (uncompressed_heap_blob.as_slice()); // check buf len. diff --git a/crates/codec/src/decoding/v2/zstd.rs b/crates/codec/src/decoding/v2/zstd.rs index e5969031..33667996 100644 --- a/crates/codec/src/decoding/v2/zstd.rs +++ b/crates/codec/src/decoding/v2/zstd.rs @@ -2,18 +2,46 @@ use std::io::Read; -use zstd::Decoder; +#[cfg(not(any(feature = "zstd", feature = "ruzstd")))] +compile_error!("You must enable exactly one of the `zstd` or `ruzstd` features"); +#[cfg(all(feature = "zstd", feature = "ruzstd"))] +compile_error!("Features `zstd` and `ruzstd` are mutually exclusive"); /// The ZSTD magic number for zstd compressed data header. const ZSTD_MAGIC_NUMBER: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd]; +/// Result type for Zstd operations. +type Result = std::result::Result; + +/// Zstd error type. +#[derive(Debug)] +pub struct ZstdError(Box); + +impl std::fmt::Display for ZstdError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ZstdError: {}", self.0) + } +} + +impl std::error::Error for ZstdError {} + +impl ZstdError { + /// Consumes the error and returns the inner error. + pub fn into_inner(self) -> Box { + self.0 + } +} + /// Uncompress the provided data. -pub fn decompress_blob_data(data: &[u8]) -> Vec { +#[cfg(feature = "zstd")] +pub fn decompress_blob_data(data: &[u8]) -> Result> { + use zstd::Decoder; let mut header_data = ZSTD_MAGIC_NUMBER.to_vec(); + header_data.extend_from_slice(data); // init decoder and owned output data. - let mut decoder = Decoder::new(header_data.as_slice()).unwrap(); + let mut decoder = Decoder::new(header_data.as_slice()).map_err(|e| ZstdError(Box::new(e)))?; // heuristic: use data length as the allocated output capacity. let mut output = Vec::with_capacity(header_data.len()); @@ -29,5 +57,23 @@ pub fn decompress_blob_data(data: &[u8]) -> Vec { output.extend_from_slice(&dst[..size]); } - output + Ok(output) +} + +/// Uncompress the provided data. +#[cfg(feature = "ruzstd")] +pub fn decompress_blob_data(data: &[u8]) -> Result> { + use ruzstd::decoding::StreamingDecoder; + + let mut header_data = ZSTD_MAGIC_NUMBER.to_vec(); + header_data.extend_from_slice(data); + + // init decoder and owned output data. + let mut decoder = + StreamingDecoder::new(header_data.as_slice()).map_err(|e| ZstdError(Box::new(e)))?; + // heuristic: use data length as the allocated output capacity. + let mut output = Vec::with_capacity(header_data.len()); + decoder.read_to_end(&mut output).map_err(|e| ZstdError(Box::new(e)))?; + + Ok(output) } diff --git a/crates/codec/src/decoding/v4/mod.rs b/crates/codec/src/decoding/v4/mod.rs index 02b79775..4ce64406 100644 --- a/crates/codec/src/decoding/v4/mod.rs +++ b/crates/codec/src/decoding/v4/mod.rs @@ -31,7 +31,7 @@ pub fn decode_v4(calldata: &[u8], blob: &[u8]) -> Result { debug_assert!(is_compressed == 1 || is_compressed == 0, "incorrect compressed byte flag"); let buf = if is_compressed == 1 { - heap_blob = decompress_blob_data(&heap_blob[1..]); + heap_blob = decompress_blob_data(&heap_blob[1..])?; &mut heap_blob.as_slice() } else { &mut (&heap_blob[1..]) diff --git a/crates/codec/src/decoding/v7/mod.rs b/crates/codec/src/decoding/v7/mod.rs index e98e38cc..d22f35d3 100644 --- a/crates/codec/src/decoding/v7/mod.rs +++ b/crates/codec/src/decoding/v7/mod.rs @@ -49,7 +49,7 @@ pub fn decode_v7(blob: &[u8]) -> Result { // uncompress if necessary. let buf = if is_compressed == 1 { - heap_blob = decompress_blob_data(buf); + heap_blob = decompress_blob_data(buf)?; &mut heap_blob.as_slice() } else { buf diff --git a/crates/codec/src/error.rs b/crates/codec/src/error.rs index 80036f4e..530efc3a 100644 --- a/crates/codec/src/error.rs +++ b/crates/codec/src/error.rs @@ -30,6 +30,8 @@ pub enum DecodingError { InvalidCommitBatchCall(#[from] InvalidCommitBatchCall), #[error("end of file")] Eof, + #[error("zstd decompression error occurred: {0}")] + ZstdDecompression(Box), #[error("decoding error occurred: {0}")] Other(Box), } @@ -46,3 +48,9 @@ impl From for DecodingError { DecodingError::Other(value.into()) } } + +impl From for DecodingError { + fn from(e: crate::decoding::v2::zstd::ZstdError) -> Self { + DecodingError::ZstdDecompression(e.into_inner()) + } +}