Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 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
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ jobs:
- features
- feature-propagation
- deny
- openvm
timeout-minutes: 30
steps:
- name: Decide whether the needed jobs succeeded or failed
Expand Down
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
3 changes: 3 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 = { version = "0.13", features = ["experimental"], default-features = false, optional = true }

[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_compression",
]
serde = [
"dep:serde",
Expand All @@ -66,3 +68,4 @@ serde = [
"scroll-alloy-hardforks/serde",
"alloy-hardforks/serde",
]
zstd_compression = ["zstd"]
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 @@ -10,7 +10,7 @@ use crate::{
feynman::apply_feynman_hard_fork,
},
system_caller::ScrollSystemCaller,
ScrollEvm, ScrollEvmFactory, ScrollTransactionIntoTxEnv,
FromTxWithCompression, ScrollEvm, ScrollEvmFactory, ScrollTransactionIntoTxEnv, ToCompressed,
};
use alloc::{boxed::Box, format, vec::Vec};

Expand Down Expand Up @@ -38,6 +38,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 @@ -91,6 +94,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 @@ -285,7 +325,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
11 changes: 9 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::{
compute_compression_ratio, FromTxWithCompression, ScrollTransactionIntoTxEnv, ToCompressed,
WithCompression,
};

mod system_caller;

Expand Down Expand Up @@ -42,6 +45,9 @@ use revm_scroll::{
ScrollSpecId, ScrollTransaction,
};

/// Re-export `TX_L1_FEE_PRECISION_U256` from `revm-scroll` for convenience.
pub use revm_scroll::l1block::TX_L1_FEE_PRECISION_U256;

/// Scroll EVM implementation.
#[allow(missing_debug_implementations)]
pub struct ScrollEvm<DB: Database, I, P = ScrollPrecompileProvider> {
Expand Down Expand Up @@ -148,6 +154,7 @@ 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.

compression_ratio: Some(TX_L1_FEE_PRECISION_U256),
};

let mut gas_limit = tx.base.gas_limit;
Expand Down
214 changes: 214 additions & 0 deletions crates/scroll/alloy/evm/src/tx/compression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use super::FromRecoveredTx;
use crate::ScrollTransactionIntoTxEnv;
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};
pub use zstd_compression::compute_compression_ratio;

#[cfg(feature = "zstd_compression")]
mod zstd_compression {
use super::*;
use std::io::Write;
use zstd::{
stream::Encoder,
zstd_safe::{CParameter, ParamSwitch},
};

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

fn compressor(target_block_size: u32) -> Encoder<'static, Vec<u8>> {
let mut encoder = Encoder::new(Vec::new(), 0).expect("Failed to create zstd encoder");
encoder
.set_parameter(CParameter::LiteralCompressionMode(ParamSwitch::Disable))
.expect("Failed to set literal compression mode");
encoder
.set_parameter(CParameter::WindowLog(CL_WINDOW_LIMIT))
.expect("Failed to set window log");
encoder
.set_parameter(CParameter::TargetCBlockSize(target_block_size))
.expect("Failed to set target block size");
encoder.include_checksum(false).expect("Failed to disable checksum");
encoder.include_magicbytes(false).expect("Failed to disable magic bytes");
encoder.include_dictid(false).expect("Failed to disable dictid");
encoder.include_contentsize(true).expect("Failed to include content size");
encoder
}

/// Computes the compression ratio for the provided bytes.
///
/// This is computed as:
/// `(original_size * TX_L1_FEE_PRECISION_U256) / compressed_size`
pub fn compute_compression_ratio<T: AsRef<[u8]>>(bytes: &T) -> U256 {
// Instantiate the compressor
let mut compressor = compressor(CL_WINDOW_LIMIT);

// Set the pledged source size to the length of the bytes and write the bytes to the
// compressor.
let original_bytes_len = bytes.as_ref().len();
compressor
.set_pledged_src_size(Some(original_bytes_len as u64))
.expect("failed to set pledged source size");
compressor.write_all(bytes.as_ref()).expect("failed to write bytes to compressor");

// Finish the compression and get the result.
let result = compressor.finish().expect("failed to finish compression");
Comment on lines +59 to +62

Choose a reason for hiding this comment

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

Should we propagate error instead of panicking?

Choose a reason for hiding this comment

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

This might be problematic, since from_encoded_tx and from_recovered_tx are not fallible... My understanding is that these should not fail in practice.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the challenge I encountered. The BlockExecutor does not have a way to propagate errors, so at some point we will need to unwrap the error and panic. Given that we currently execute this function on already built blocks then it should not be possible to encounter a panic in practice. In the payload builder we may need to be more careful in case there is some way a user can manipulate the input transaction to result in a panic. However, I still believe this is unlikely.


// compute the compression ratio
let original_len = U256::from(original_bytes_len).saturating_mul(TX_L1_FEE_PRECISION_U256);
let compressed_len = U256::from(result.len());
original_len.wrapping_div(compressed_len)
}
}

#[cfg(not(feature = "zstd_compression"))]
mod zstd_compression {
use super::*;

/// Computes the compression ratio for the provided bytes. This panics if the compression
/// feature is not enabled. This is to support `no_std` environments where zstd is not
/// available.
pub fn compute_compression_ratio<T: AsRef<[u8]>>(_bytes: &T) -> U256 {
panic!("Compression feature is not enabled. Please enable the 'compression' feature to use this function.");

Choose a reason for hiding this comment

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

If we don't enable zstd_compression, what can we use these crates for? Won't it always run into this runtime panic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The ScrollBlockExecutor is the primary component used by the prover to prove blocks. We can not have zstd in the dependency chain for the prover as it is not compatible with riscv / openvm. As such, we need a means of instantiating the ScrollBlockExecutor in this context and that is the purpose of the zstd_compression feature flag. The prover will compute the compression ratios in the host where zstd is available and then provide them to the guest and execute the block using:

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()
}
}

Given that we use let tx = tx.to_compressed(compression_ratio); to instantiate the WithCompressed type, the compute_compression_ratio function will never be invoked.

}
}

/// 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()
}
}
Loading
Loading