Skip to content

Commit

Permalink
feat: add flz
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Jan 22, 2025
1 parent cb2bc79 commit 2beabc0
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 7 deletions.
8 changes: 1 addition & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ op-alloy-provider = { version = "0.9.5", path = "crates/provider", default-featu
op-alloy-rpc-types = { version = "0.9.5", path = "crates/rpc-types", default-features = false }
op-alloy-rpc-types-engine = { version = "0.9.5", path = "crates/rpc-types-engine", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.9.5", path = "crates/rpc-jsonrpsee", default-features = false }
op-alloy-flz = { version = "0.9.5", path = "crates/flz", default-features = false }

# Alloy
alloy-eips = { version = "0.9.2", default-features = false }
Expand All @@ -62,9 +63,6 @@ alloy-rlp = { version = "0.3", default-features = false }
alloy-sol-types = { version = "0.8.12", default-features = false }
alloy-primitives = { version = "0.8.12", default-features = false }

# Revm
revm = "19.0.0"

# Serde
serde_repr = "0.1"
serde = { version = "1.0", default-features = false, features = [
Expand All @@ -79,10 +77,6 @@ snap = "1.1.1"
bincode = "1.3.3"
ethereum_ssz = "0.8"

# Compression
miniz_oxide = "0.8.2"
alloc-no-stdlib = "2.0.4"
brotli = { version = "7.0.0", default-features = false }

# rpc
jsonrpsee = { version = "0.24", features = [
Expand Down
27 changes: 27 additions & 0 deletions crates/flz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "op-alloy-flz"
description = "Tiny FastLZ compression library"

version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
exclude.workspace = true

[lints]
workspace = true

[dependencies]
fastlz = { version = "0.1.0", optional = true }

[dev-dependencies]
alloy-primitives.workspace = true
rstest.workspace = true

[features]
default = []
compress = ["dep:fastlz"]
decompress = ["dep:fastlz"]
3 changes: 3 additions & 0 deletions crates/flz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## `op-alloy-flz`

Tiny crate containing FastLZ compression length utilities.
192 changes: 192 additions & 0 deletions crates/flz/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#![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"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![no_std]

#[cfg(feature = "compress")]
pub use fastlz::compress;

#[cfg(feature = "decompress")]
pub use fastlz::decompress;

/// Cost per zero byte.
pub const ZERO_BYTE_COST: u64 = 4;

/// Cost per non-zero byte.
pub const NON_ZERO_BYTE_COST: u64 = 16;


/// <https://github.com/ethereum-optimism/op-geth/blob/647c346e2bef36219cc7b47d76b1cb87e7ca29e4/core/types/rollup_cost.go#L79>
pub const L1_COST_FASTLZ_COEF: u64 = 836_500;

/// <https://github.com/ethereum-optimism/op-geth/blob/647c346e2bef36219cc7b47d76b1cb87e7ca29e4/core/types/rollup_cost.go#L78>
/// Inverted to be used with `saturating_sub`.
pub const L1_COST_INTERCEPT: u64 = 42_585_600;

/// <https://github.com/ethereum-optimism/op-geth/blob/647c346e2bef36219cc7b47d76b1cb87e7ca29e4/core/types/rollup_cost.go#82>
pub const MIN_TX_SIZE_SCALED: u64 = 100 * 1_000_000;


/// Calculate the data gas for posting the transaction on L1.
///
/// In fjord, Calldata costs 16 gas per byte after compression.
pub fn data_gas_fjord(input: &[u8]) -> u64 {
let estimated_size = tx_estimated_size_fjord(input);
estimated_size
.saturating_mul(NON_ZERO_BYTE_COST)
.wrapping_div(1_000_000)
}

/// Calculate the estimated compressed transaction size in bytes, scaled by 1e6.
/// This value is computed based on the following formula:
/// max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
pub fn tx_estimated_size_fjord(input: &[u8]) -> u64 {
let fastlz_size = flz_compress_len(input) as u64;

fastlz_size
.saturating_mul(L1_COST_FASTLZ_COEF)
.saturating_sub(L1_COST_INTERCEPT)
.max(MIN_TX_SIZE_SCALED)
}

/// Returns the length of the data after compression through FastLZ.
///
/// The u32s match op-geth's Go port.
///
/// <https://github.com/Vectorized/solady/blob/5315d937d79b335c668896d7533ac603adac5315/js/solady.js>
/// <https://github.com/ethereum-optimism/op-geth/blob/647c346e2bef36219cc7b47d76b1cb87e7ca29e4/core/types/rollup_cost.go#L411>
pub fn flz_compress_len(input: &[u8]) -> u32 {
let mut idx: u32 = 2;

let idx_limit: u32 = if input.len() < 13 { 0 } else { input.len() as u32 - 13 };

let mut anchor = 0;

let mut size = 0;

let mut htab = [0; 8192];

while idx < idx_limit {
let mut r: u32;
let mut distance: u32;

loop {
let seq = u24(input, idx);
let hash = hash(seq);
r = htab[hash as usize];
htab[hash as usize] = idx;
distance = idx - r;
if idx >= idx_limit {
break;
}
idx += 1;
if distance < 8192 && seq == u24(input, r) {
break;
}
}

if idx >= idx_limit {
break;
}

idx -= 1;

if idx > anchor {
size = literals(idx - anchor, size);
}

let len = cmp(input, r + 3, idx + 3, idx_limit + 9);
size = flz_match(len, size);

idx = set_next_hash(&mut htab, input, idx + len);
idx = set_next_hash(&mut htab, input, idx);
anchor = idx;
}

literals(input.len() as u32 - anchor, size)
}

const fn literals(r: u32, size: u32) -> u32 {
let size = size + 0x21 * (r / 0x20);
let r = r % 0x20;
if r != 0 {
size + r + 1
} else {
size
}
}

const fn cmp(input: &[u8], p: u32, q: u32, r: u32) -> u32 {
let mut l = 0;
let mut r = r - q;
while l < r {
if input[(p + l) as usize] != input[(q + l) as usize] {
r = 0;
}
l += 1;
}
l
}

const fn flz_match(l: u32, size: u32) -> u32 {
let l = l - 1;
let size = size + (3 * (l / 262));
if l % 262 >= 6 {
size + 3
} else {
size + 2
}
}

fn set_next_hash(htab: &mut [u32; 8192], input: &[u8], idx: u32) -> u32 {
htab[hash(u24(input, idx)) as usize] = idx;
idx + 1
}

const fn hash(v: u32) -> u16 {
let hash = (v as u64 * 2654435769) >> 19;
hash as u16 & 0x1fff
}

fn u24(input: &[u8], idx: u32) -> u32 {
u32::from(input[idx as usize])
+ (u32::from(input[(idx + 1) as usize]) << 8)
+ (u32::from(input[(idx + 2) as usize]) << 16)
}

#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::vec::Vec;
use rstest::rstest;
use alloy_primitives::bytes;

#[rstest]
#[case::empty(&[], 0)]
#[case::thousand_zeros(&[0; 1000], 21)]
#[case::thousand_forty_twos(&[42; 1000], 21)]
#[case::short_hex(&bytes!("FACADE"), 4)]
#[case::sample_contract_call(&bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e"), 202)]
#[case::base_0x5dadeb52979f29fc7a7494c43fdabc5be1d8ff404f3aafe93d729fa8e5d00769(&bytes!("b9047c02f904788221050883036ee48409c6c87383037f6f941195cf65f83b3a5768f3c496d3a05ad6412c64b78644364c5bb000b90404d123b4d80000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000f6476f90447748c19248ccaa31e6b8bfda4eb9d830f5f47df7f0998f7c2123d9e6137761b75d3184efb0f788e3b14516000000000000000000000000000000000000000000000000000044364c5bb000000000000000000000000000f38e53bd45c8225a7c94b513beadaa7afe5d222d0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d6574614d61736b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656852577a743347745961776343347564745657557233454c587261436746434259416b66507331696f48610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd0d83d9e840f8e27d5c2e365fd365ff1c05b2480000000000000000000000000000000000000000000000000000000000000ce40000000000000000000000000000000000000000000000000000000000000041e4480d358dbae20880960a0a464d63b06565a0c9f9b1b37aa94b522247b23ce149c81359bf4239d1a879eeb41047ec710c15f5c0f67453da59a383e6abd742971c00000000000000000000000000000000000000000000000000000000000000c001a0b57f0ff8516ea29cb26a44ac5055a5420847d1e16a8e7b03b70f0c02291ff2d5a00ad3771e5f39ccacfff0faa8c5d25ef7a1c179f79e66e828ffddcb994c8b512e"), 471)]
fn test_flz_compress_len(#[case] input: &[u8], #[case] expected: u32) {
assert_eq!(flz_compress_len(input), expected);
}

#[test]
fn test_flz_compress_len_no_repeats() {
let mut input = Vec::new();
let mut len = 0;

for i in 0..256 {
input.push(i as u8);
let prev_len = len;
len = flz_compress_len(&input);
assert!(len > prev_len);
}
}
}

0 comments on commit 2beabc0

Please sign in to comment.