Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
94c0531
feat: feynman compression
frisitano Jun 23, 2025
c1f6bb2
Merge branch 'scroll' into feat/feynman-compression
frisitano Jun 23, 2025
2848551
update feynman compression
frisitano Jun 24, 2025
1b3e1e9
make no_std compat
frisitano Jun 25, 2025
730332e
lint
frisitano Jun 25, 2025
6a86b0d
disable unit book github workflow
frisitano Jun 25, 2025
8359a9f
Merge branch 'scroll' into feat/feynman-compression
frisitano Jun 25, 2025
6da4f89
update Cargo.lock
frisitano Jun 25, 2025
c01e6b9
migrate to zstd-safe
frisitano Jun 25, 2025
c7af59c
fix CI
frisitano Jun 25, 2025
bbec513
set zstd-safe default-features = false
frisitano Jun 25, 2025
f7df34c
lint
frisitano Jun 25, 2025
444f636
repoint scroll-revm dep
frisitano Jun 25, 2025
d73e95b
Merge branch 'scroll' into feat/feynman-compression
frisitano Jun 25, 2025
1ab4fb6
use U256 for compression ratio calculation
frisitano Jun 25, 2025
cbc6502
revert to using zstd library
frisitano Jun 26, 2025
0a1080f
cleanup
frisitano Jun 26, 2025
7fd42bb
disable unit book github workflow
frisitano Jun 26, 2025
0af3bc8
cleanup
frisitano Jun 26, 2025
5db5c3b
Merge branch 'scroll' into feat/feynman-compression
Thegaram Jun 26, 2025
094530a
fix naming
Thegaram Jun 26, 2025
89d2bf0
add comment
Thegaram Jun 26, 2025
f6ed73e
fix compression and add test cases
Thegaram Jun 26, 2025
44c1faa
renamings
Thegaram Jun 26, 2025
c4c493d
use exact zstd version
Thegaram Jun 26, 2025
bb61d77
update scroll-revm version
Thegaram Jun 26, 2025
50e514e
add missing comment
Thegaram Jun 26, 2025
29ed160
update tests
Thegaram Jun 26, 2025
acccef6
update tests
Thegaram Jun 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ revm-context = { git = "https://github.com/scroll-tech/revm", branch = "feat/ret
revm-context-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false }
revm-database-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false }
op-revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false }
revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", default-features = false }
revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", branch = "feat/feynman-rollup-fee-v2", default-features = false }
revm-inspectors = "0.23.0"

# eth
Expand Down
2 changes: 2 additions & 0 deletions crates/scroll/alloy/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ scroll-alloy-hardforks = { workspace = true, default-features = false }
# misc
auto_impl = { workspace = true, default-features = false }
serde = { workspace = true, default-features = false, features = ["derive"], optional = true }
zstd-safe = { version = "7.0.0", features = ["experimental"], default-features = false }

[dev-dependencies]
alloy-hardforks.workspace = true
Expand All @@ -54,6 +55,7 @@ std = [
"reth-evm/std",
"reth-scroll-chainspec/std",
"reth-scroll-evm/std",
"zstd-safe/std",
]
serde = [
"dep:serde",
Expand Down
49 changes: 47 additions & 2 deletions crates/scroll/alloy/evm/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod receipt_builder;
use crate::{
block::curie::{apply_curie_hard_fork, L1_GAS_PRICE_ORACLE_ADDRESS},
system_caller::ScrollSystemCaller,
ScrollEvm, ScrollEvmFactory, ScrollTransactionIntoTxEnv,
FromTxWithCompression, ScrollEvm, ScrollEvmFactory, ScrollTransactionIntoTxEnv, ToCompressed,
};
use alloc::{boxed::Box, format, vec::Vec};

Expand Down Expand Up @@ -34,6 +34,9 @@ use revm_scroll::builder::ScrollContext;
use scroll_alloy_consensus::L1_MESSAGE_TRANSACTION_TYPE;
use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks};

/// A cache for transaction compression ratios.
pub type ScrollTxCompressionRatios = Vec<U256>;

/// Context for Scroll Block Execution.
#[derive(Debug, Default, Clone)]
pub struct ScrollBlockExecutionCtx {
Expand Down Expand Up @@ -87,6 +90,43 @@ where
}
}

impl<'db, DB, E, R, Spec> ScrollBlockExecutor<E, R, Spec>
where
DB: Database + 'db,
E: EvmExt<
DB = &'db mut State<DB>,
Tx: FromRecoveredTx<R::Transaction>
+ FromTxWithEncoded<R::Transaction>
+ FromTxWithCompression<R::Transaction>,
>,
R: ScrollReceiptBuilder<Transaction: Transaction + Encodable2718, Receipt: TxReceipt>,
Spec: ScrollHardforks,
{
/// Executes all transactions in a block, applying pre and post execution changes. The provided
/// transaction compression ratios are expected to be in the same order as the
/// transactions.
pub fn execute_block_with_compression_cache(
mut self,
transactions: impl IntoIterator<
Item = impl ExecutableTx<Self> + ToCompressed<<Self as BlockExecutor>::Transaction>,
>,
compression_ratios: ScrollTxCompressionRatios,
) -> Result<BlockExecutionResult<R::Receipt>, BlockExecutionError>
where
Self: Sized,
{
self.apply_pre_execution_changes()?;

for (tx, compression_ratio) in transactions.into_iter().zip(compression_ratios.into_iter())
{
let tx = tx.to_compressed(compression_ratio);
self.execute_transaction(&tx)?;
}

self.apply_post_execution_changes()
}
}

impl<'db, DB, E, R, Spec> BlockExecutor for ScrollBlockExecutor<E, R, Spec>
where
DB: Database + 'db,
Expand Down Expand Up @@ -267,7 +307,12 @@ where
fn l1_fee(&self) -> Option<U256> {
let l1_block_info = &self.ctx().chain;
let transaction_rlp_bytes = self.ctx().tx.rlp_bytes.as_ref()?;
Some(l1_block_info.calculate_tx_l1_cost(transaction_rlp_bytes, self.ctx().cfg.spec))
let compression_ratio = self.ctx().tx.compression_ratio;
Some(l1_block_info.calculate_tx_l1_cost(
transaction_rlp_bytes,
self.ctx().cfg.spec,
compression_ratio,
))
}
}

Expand Down
10 changes: 8 additions & 2 deletions crates/scroll/alloy/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
mod block;
pub use block::{
curie, EvmExt, ReceiptBuilderCtx, ScrollBlockExecutionCtx, ScrollBlockExecutor,
ScrollBlockExecutorFactory, ScrollReceiptBuilder,
ScrollBlockExecutorFactory, ScrollReceiptBuilder, ScrollTxCompressionRatios,
};

mod tx;
pub use tx::ScrollTransactionIntoTxEnv;
pub use tx::{
compress_zstd, compressor_zstd, compute_zstd_compression_ratio, FromTxWithCompression,
ScrollTransactionIntoTxEnv, ToCompressed, WithCompression,
};

mod system_caller;

Expand All @@ -38,6 +41,7 @@ use revm_scroll::{
ScrollContext,
},
instructions::ScrollInstructions,
l1block::TX_L1_FEE_PRECISION_U256,
precompile::ScrollPrecompileProvider,
ScrollSpecId, ScrollTransaction,
};
Expand Down Expand Up @@ -148,6 +152,8 @@ where
authorization_list: Default::default(),
},
rlp_bytes: Some(Default::default()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but could it cause any issues that tx.base.data and tx.rlp_bytes don't match?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// TODO: What makes sense in the context of a system call?
compression_ratio: Some(TX_L1_FEE_PRECISION_U256),
};

let mut gas_limit = tx.base.gas_limit;
Expand Down
196 changes: 196 additions & 0 deletions crates/scroll/alloy/evm/src/tx/compression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use super::FromRecoveredTx;
use crate::ScrollTransactionIntoTxEnv;
use alloc::vec;
use alloy_consensus::transaction::Recovered;
use alloy_eips::{Encodable2718, Typed2718};
use alloy_evm::{IntoTxEnv, RecoveredTx};
use alloy_primitives::{Address, Bytes, TxKind, U256};
use revm::context::TxEnv;
use revm_scroll::l1block::TX_L1_FEE_PRECISION_U256;
use scroll_alloy_consensus::{ScrollTxEnvelope, TxL1Message};
use zstd_safe::{compress_bound, CCtx, CParameter, FrameFormat, ParamSwitch};

/// The maximum size of the compression window in bytes (`2^CL_WINDOW_LIMIT`).
const CL_WINDOW_LIMIT: u32 = 22;

/// The compression level used for zstd compression.
const COMPRESSION_LEVEL: i32 = 3;

// TODO: Can we instantiate the compressor once and reuse it?

/// Creates a zstd compressor with the specified target block size.
pub fn compressor_zstd(target_block_size: u32) -> CCtx<'static> {
let mut ctx = CCtx::default();
ctx.set_parameter(CParameter::LiteralCompressionMode(ParamSwitch::Disable))
.expect("Failed to set literal compression mode");
ctx.set_parameter(CParameter::WindowLog(CL_WINDOW_LIMIT)).expect("Failed to set window log");
ctx.set_parameter(CParameter::TargetCBlockSize(target_block_size))
.expect("Failed to set target block size");
ctx.set_parameter(CParameter::ChecksumFlag(false)).expect("Failed to set checksum flag");
ctx.set_parameter(CParameter::Format(FrameFormat::Magicless))
.expect("msg: Failed to set frame format");
ctx.set_parameter(CParameter::DictIdFlag(false)).expect("Failed to set dictid flag");
ctx.set_parameter(CParameter::ContentSizeFlag(true)).expect("Failed to set content size flag");
ctx
}

/// Compresses the input data using zstd compression.
pub fn compress_zstd<T: AsRef<[u8]>>(input: &T) -> Vec<u8> {
let mut compressor = compressor_zstd(CL_WINDOW_LIMIT);
let max_compressed_size = compress_bound(input.as_ref().len());
let mut output_buf = vec![0u8; max_compressed_size];
let output_slice = &mut output_buf[..];
let compressed_bytes = compressor
.compress(output_slice, input.as_ref(), COMPRESSION_LEVEL)
.expect("Failed to compress data");
output_buf.truncate(compressed_bytes);
output_buf
}

/// Computes the compression ratio of the bytes after compressing them with zstd.
pub fn compute_zstd_compression_ratio<T: AsRef<[u8]>>(bytes: &T) -> U256 {
// Compress the bytes
let compressed_bytes = compress_zstd(bytes);

// Compute the compression ratio
let original_len = U256::from(bytes.as_ref().len()).saturating_mul(TX_L1_FEE_PRECISION_U256);
let compressed_len = U256::from(compressed_bytes.len());
let compression_ratio = original_len.wrapping_div(compressed_len);

compression_ratio
}
/// A generic wrapper for a type that includes a compression ratio and encoded bytes.
#[derive(Debug, Clone)]
pub struct WithCompression<T> {
value: T,
compression_ratio: U256,
encoded_bytes: Bytes,
}

/// A trait for types that can be constructed from a transaction, its sender, encoded bytes and
/// compression ratio.
pub trait FromTxWithCompression<Tx> {
/// Builds a `TxEnv` from a transaction, its sender, encoded transaction bytes, and a
/// compression ratio.
fn from_compressed_tx(
tx: &Tx,
sender: Address,
encoded: Bytes,
compression_ratio: Option<U256>,
) -> Self;
}

impl<TxEnv, T> FromTxWithCompression<&T> for TxEnv
where
TxEnv: FromTxWithCompression<T>,
{
fn from_compressed_tx(
tx: &&T,
sender: Address,
encoded: Bytes,
compression_ratio: Option<U256>,
) -> Self {
TxEnv::from_compressed_tx(tx, sender, encoded, compression_ratio)
}
}

impl<T, TxEnv: FromTxWithCompression<T>> IntoTxEnv<TxEnv> for WithCompression<Recovered<T>> {
fn into_tx_env(self) -> TxEnv {
let recovered = &self.value;
TxEnv::from_compressed_tx(
recovered.inner(),
recovered.signer(),
self.encoded_bytes.clone(),
Some(self.compression_ratio),
)
}
}

impl<T, TxEnv: FromTxWithCompression<T>> IntoTxEnv<TxEnv> for &WithCompression<Recovered<T>> {
fn into_tx_env(self) -> TxEnv {
let recovered = &self.value;
TxEnv::from_compressed_tx(
recovered.inner(),
recovered.signer(),
self.encoded_bytes.clone(),
Some(self.compression_ratio),
)
}
}

impl<T, TxEnv: FromTxWithCompression<T>> IntoTxEnv<TxEnv> for WithCompression<&Recovered<T>> {
fn into_tx_env(self) -> TxEnv {
let recovered = &self.value;
TxEnv::from_compressed_tx(
recovered.inner(),
*recovered.signer(),
self.encoded_bytes.clone(),
Some(self.compression_ratio),
)
}
}

impl<T, TxEnv: FromTxWithCompression<T>> IntoTxEnv<TxEnv> for &WithCompression<&Recovered<T>> {
fn into_tx_env(self) -> TxEnv {
let recovered = &self.value;
TxEnv::from_compressed_tx(
recovered.inner(),
*recovered.signer(),
self.encoded_bytes.clone(),
Some(self.compression_ratio),
)
}
}

impl FromTxWithCompression<ScrollTxEnvelope> for ScrollTransactionIntoTxEnv<TxEnv> {
fn from_compressed_tx(
tx: &ScrollTxEnvelope,
caller: Address,
encoded: Bytes,
compression_ratio: Option<U256>,
) -> Self {
let base = match &tx {
ScrollTxEnvelope::Legacy(tx) => TxEnv::from_recovered_tx(tx.tx(), caller),
ScrollTxEnvelope::Eip2930(tx) => TxEnv::from_recovered_tx(tx.tx(), caller),
ScrollTxEnvelope::Eip1559(tx) => TxEnv::from_recovered_tx(tx.tx(), caller),
ScrollTxEnvelope::Eip7702(tx) => TxEnv::from_recovered_tx(tx.tx(), caller),
ScrollTxEnvelope::L1Message(tx) => {
let TxL1Message { to, value, gas_limit, input, queue_index: _, sender: _ } = &**tx;
TxEnv {
tx_type: tx.ty(),
caller,
gas_limit: *gas_limit,
kind: TxKind::Call(*to),
value: *value,
data: input.clone(),
..Default::default()
}
}
};

Self::new(base, Some(encoded), compression_ratio)
}
}

/// A trait that allows a type to be converted into [`WithCompression`].
pub trait ToCompressed<T> {
/// Converts the type into a [`WithCompression`] instance using the provided compression ratio.
fn to_compressed(&self, compression_ratio: U256) -> WithCompression<Recovered<&T>>;
}

impl<T: Encodable2718> ToCompressed<T> for Recovered<&T> {
fn to_compressed(&self, compression_ratio: U256) -> WithCompression<Recovered<&T>> {
let encoded_bytes = self.inner().encoded_2718();
WithCompression { value: *self, compression_ratio, encoded_bytes: encoded_bytes.into() }
}
}

impl<Tx, T: RecoveredTx<Tx>> RecoveredTx<Tx> for WithCompression<T> {
fn tx(&self) -> &Tx {
self.value.tx()
}

fn signer(&self) -> &Address {
self.value.signer()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ use revm::context::{
use revm_scroll::ScrollTransaction;
use scroll_alloy_consensus::{ScrollTxEnvelope, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE};

mod compression;
pub use compression::{
compress_zstd, compressor_zstd, compute_zstd_compression_ratio, FromTxWithCompression,
ToCompressed, WithCompression,
};

/// This structure wraps around a [`ScrollTransaction`] and allows us to implement the [`IntoTxEnv`]
/// trait. This can be removed when the interface is improved. Without this wrapper, we would need
/// to implement the trait in `revm-scroll`, which adds a dependency on `alloy-evm` in the crate.
Expand All @@ -21,8 +27,8 @@ pub struct ScrollTransactionIntoTxEnv<T: Transaction>(ScrollTransaction<T>);

impl<T: Transaction> ScrollTransactionIntoTxEnv<T> {
/// Returns a new [`ScrollTransactionIntoTxEnv`].
pub fn new(base: T, rlp_bytes: Option<Bytes>) -> Self {
Self(ScrollTransaction::new(base, rlp_bytes))
pub fn new(base: T, rlp_bytes: Option<Bytes>, compression_ratio: Option<U256>) -> Self {
Self(ScrollTransaction::new(base, rlp_bytes, compression_ratio))
}
}

Expand Down Expand Up @@ -154,7 +160,8 @@ impl FromTxWithEncoded<ScrollTxEnvelope> for ScrollTransactionIntoTxEnv<TxEnv> {
};

let encoded = (!tx.is_l1_message()).then_some(encoded);
Self::new(base, encoded)
let compression_ratio = encoded.as_ref().map(compute_zstd_compression_ratio);
Self::new(base, encoded, compression_ratio)
}
}

Expand Down Expand Up @@ -261,6 +268,7 @@ impl FromRecoveredTx<ScrollTxEnvelope> for ScrollTransactionIntoTxEnv<TxEnv> {
};

let rlp_bytes = (!tx.is_l1_message()).then_some(envelope.into());
Self::new(base, rlp_bytes)
let compression_ratio = rlp_bytes.as_ref().map(compute_zstd_compression_ratio);
Self::new(base, rlp_bytes, compression_ratio)
}
}
Loading
Loading