-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
223 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## `op-alloy-flz` | ||
|
||
Tiny crate containing FastLZ compression length utilities. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |