Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,12 @@ jobs:
include:
- type: wasm
target: wasm32-unknown-unknown
exclude: scroll-engine,scroll-wire,scroll-bridge,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-indexer
exclude: |
scroll-engine,scroll-wire,scroll-bridge,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-indexer
- type: riscv
target: riscv32imac-unknown-none-elf
exclude: scroll-engine,scroll-wire,scroll-bridge,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-indexer
exclude: |
scroll-engine,scroll-wire,scroll-bridge,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-indexer,scroll-codec
steps:
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
Expand Down
15 changes: 15 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"bin/bridge",
"crates/database/db",
"crates/database/migration",
"crates/codec",
"crates/engine",
"crates/indexer",
"crates/l1",
Expand Down Expand Up @@ -150,6 +151,7 @@ rollup-node-manager = { path = "crates/node" }
rollup-node-primitives = { path = "crates/primitives" }
rollup-node-watcher = { path = "crates/watcher" }
scroll-db = { path = "crates/database/db" }
scroll-codec = { path = "crates/codec" }
scroll-engine = { path = "crates/engine" }
scroll-l1 = { path = "crates/l1" }
scroll-network = { path = "crates/network" }
Expand Down
26 changes: 26 additions & 0 deletions crates/codec/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "scroll-codec"
version = "0.1.0"
edition = "2024"

[dependencies]
# alloy
alloy-eips.workspace = true
alloy-primitives.workspace = true
alloy-rlp = { version = "0.3", default-features = false }
alloy-sol-types = { version = "0.8", default-features = false }

# scroll
scroll-l1.workspace = true

# misc
derive_more = { version = "2.0", default-features = false }
eyre = { workspace = true, optional = true }
thiserror = { version = "2.0", default-features = false }
zstd = "0.13"

[dev-dependencies]
eyre.workspace = true

[features]
test-utils = ["dep:eyre", "scroll-l1/test-utils"]
36 changes: 36 additions & 0 deletions crates/codec/src/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! L2 block and block context implementations.

use std::vec::Vec;

use alloy_primitives::{Bytes, U256};

/// A L2 block.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct L2Block {
/// The RLP-encoded transactions of the block.
pub transactions: Vec<Bytes>,
/// The context for the block.
pub context: BlockContext,
}

impl L2Block {
/// Returns a new instance of a [`L2Block`].
pub fn new(transactions: Vec<Bytes>, context: BlockContext) -> Self {
Self { transactions, context }
}
}

/// The block's context.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BlockContext {
/// The block number.
pub number: u64,
/// The block timestamp.
pub timestamp: u64,
/// The block base fee.
pub base_fee: U256,
/// The block gas limit.
pub gas_limit: u64,
/// The block's l1 message count.
pub num_l1_messages: u16,
}
50 changes: 50 additions & 0 deletions crates/codec/src/decoding/blob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use core::slice::Iter;

/// An iterator over a blob. The structure implements the iterator trait and will skip a byte every
/// 32 bytes. This byte is wasted due to the use of 32 bytes per field element in the blob,
/// but every field element needing to be smaller than the BLS modulus.
#[derive(Debug, Clone)]
pub struct BlobSliceIter<'a> {
iterator: Iter<'a, u8>,
count: usize,
}

impl<'a> BlobSliceIter<'a> {
/// Returns a [`BlobSliceIter`] from the provided iterator.
pub fn from_blob_slice(blob: &'a [u8]) -> BlobSliceIter<'a> {
Self { iterator: blob.iter(), count: 0 }
}
}

impl<'a> Iterator for BlobSliceIter<'a> {
type Item = &'a u8;

fn next(&mut self) -> Option<Self::Item> {
if self.count % 32 == 0 {
let _ = self.iterator.next();
self.count += 1;
}
self.count += 1;
self.iterator.next()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::decoding::test_utils::read_to_bytes;
use alloy_primitives::bytes;

#[test]
fn test_should_skip_unused_blob_bytes() -> eyre::Result<()> {
let blob = read_to_bytes("./src/testdata/blob_v1.bin")?;
let iterator = BlobSliceIter::from_blob_slice(&blob);

let val = iterator.take(256).copied().collect::<Vec<_>>();
let expected = bytes!("000b00000e3a0000206d00005efd00003ee1000026ca00000a480000218f0000417c000038110000305b00000d3e00000000000000000000000000000000f88d83073c99840c6aed6a82a4e494530000000000000000000000000000000000000280a4bede39b50000000000000000000000000000000000000000000000000000000015ea5f7283104ec3a03992018538e02f45171e6826c19c046e1bd79286f07475fa9103d4319af5f0caa072d510a28ac11e020807347ab425b6fd3b1b012d6631760d5d3a9bf8ed2c22c3f88d83073c9a840c6aed6a82a4e494530000000000000000000000000000000000000280a4bede39b500000000000000000000").to_vec();

assert_eq!(val, expected);

Ok(())
}
}
23 changes: 23 additions & 0 deletions crates/codec/src/decoding/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// Copies the provided slice into $ty using $ty::from_be_bytes and advances the buffer.
#[macro_export]
macro_rules! from_be_bytes_slice_and_advance_buf {
($ty: ty, $slice: expr) => {{ $crate::from_be_bytes_slice_and_advance_buf!($ty, ::std::mem::size_of::<$ty>(), $slice) }};
($ty:ty, $size: expr, $slice: expr) => {{
let mut arr = [0u8; ::std::mem::size_of::<$ty>()];
let size = $size;
let size_of = ::std::mem::size_of::<$ty>();
arr[size_of - size..].copy_from_slice(&$slice[0..size]);
::alloy_primitives::bytes::Buf::advance($slice, size);
<$ty>::from_be_bytes(arr)
}};
}

/// Check the buffer input to have the required length. Returns an Eof error otherwise.
#[macro_export]
macro_rules! check_buf_len {
($buf: expr, $len: expr) => {{
if $buf.len() < $len {
return Err($crate::error::DecodingError::Eof)
}
}};
}
28 changes: 28 additions & 0 deletions crates/codec/src/decoding/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Decoding implementations for the commit data.

/// Blob related helpers.
pub mod blob;

mod macros;

/// V0 implementation of the decoding.
pub mod v0;

/// V1 implementation of the decoding.
pub mod v1;

/// V2 implementation of the decoding.
pub mod v2;

/// V4 implementation of the decoding.
pub mod v4;

/// V7 implementation of the decoding.
pub mod v7;

/// Tests utils.
#[cfg(any(test, feature = "test-utils"))]
pub mod test_utils;

/// Decoding implementation for a transaction.
pub mod transaction;
7 changes: 7 additions & 0 deletions crates/codec/src/decoding/test_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use alloy_primitives::Bytes;

/// Read the file provided at `path` as a [`Bytes`].
pub fn read_to_bytes<P: AsRef<std::path::Path>>(path: P) -> eyre::Result<Bytes> {
use std::str::FromStr;
Ok(Bytes::from_str(&std::fs::read_to_string(path)?)?)
}
39 changes: 39 additions & 0 deletions crates/codec/src/decoding/transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use alloy_primitives::{Bytes, bytes::Buf};
use alloy_rlp::Header;

/// A RLP encoded transaction.
#[derive(Debug)]
pub struct Transaction(pub Bytes);

impl Transaction {
/// Tries to read from the input buffer into the [`Transaction`]. Returns [`None`] if it can't
/// read the RLP header from the buffer or the buffer length does not cover all the expected
/// payload data length.
pub(super) fn try_from_buf(buf: &mut &[u8]) -> Option<Self> {
// clone the buffer in order to avoid advancing it.
#[allow(suspicious_double_ref_op)]
let header = Header::decode(&mut buf.clone()).ok()?;
let finish = |buf: &mut &[u8], offset: usize| {
if buf.remaining() < offset {
return None
}

// copy the transaction bytes and advance the buffer.
let tx = Transaction(buf[0..offset].to_vec().into());
buf.advance(offset);
Some(tx)
};

if header.list {
// legacy tx.
finish(buf, header.length_with_payload())
} else {
// typed transaction.
let mut tx_decode = *buf;
let _ = tx_decode.get_u8();
let header = Header::decode(&mut tx_decode).ok()?;

finish(buf, header.length_with_payload() + 1)
}
}
}
53 changes: 53 additions & 0 deletions crates/codec/src/decoding/v0/block_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::{BlockContext, from_be_bytes_slice_and_advance_buf};

use alloy_primitives::{U256, bytes::Buf};

#[derive(Debug)]
pub(crate) struct BlockContextV0 {
pub(crate) number: u64,
pub(crate) timestamp: u64,
pub(crate) base_fee: U256,
pub(crate) gas_limit: u64,
pub(crate) num_transactions: u16,
pub(crate) num_l1_messages: u16,
}

impl BlockContextV0 {
pub(crate) const BYTES_LENGTH: usize = 60;

/// Tries to read from the input buffer into the [`BlockContextV0`].
/// Returns [`None`] if the buffer.len() < [`BlockContextV0::BYTES_LENGTH`].
pub(crate) fn try_from_buf(buf: &mut &[u8]) -> Option<Self> {
if buf.len() < Self::BYTES_LENGTH {
return None
}
let number = from_be_bytes_slice_and_advance_buf!(u64, buf);
let timestamp = from_be_bytes_slice_and_advance_buf!(u64, buf);

let base_fee = U256::from_be_slice(&buf[0..32]);
buf.advance(32);

let gas_limit = from_be_bytes_slice_and_advance_buf!(u64, buf);
let num_transactions = from_be_bytes_slice_and_advance_buf!(u16, buf);
let num_l1_messages = from_be_bytes_slice_and_advance_buf!(u16, buf);

Some(Self { number, timestamp, base_fee, gas_limit, num_transactions, num_l1_messages })
}

/// Returns the L2 transaction count for the block, excluding L1 messages.
pub(crate) fn transactions_count(&self) -> usize {
self.num_transactions.saturating_sub(self.num_l1_messages) as usize
}
}

impl From<BlockContextV0> for BlockContext {
fn from(value: BlockContextV0) -> Self {
Self {
number: value.number,
timestamp: value.timestamp,
base_fee: value.base_fee,
gas_limit: value.gas_limit,
num_l1_messages: value.num_l1_messages,
}
}
}
90 changes: 90 additions & 0 deletions crates/codec/src/decoding/v0/mod.rs

Large diffs are not rendered by default.

Loading
Loading