Transform a Batch into Frames

note

This example performs the reverse transformation as the frames-to-batch example.

caution

Steps and handling of types with respect to chain tip, ordering of frames, re-orgs, and more are not covered by this example. This example solely demonstrates the most trivial way to transform an individual Batch into Frames.

This example walks through transforming a Batch into Frames.

Effectively, this example demonstrates the encoding process from an L2 batch into the serialized bytes that are posted to the data availability layer.

Walkthrough

The high level transformation is the following.

Batch -> decompressed batch data -> ChannelOut -> frames[] -> bytes[]

Given the Batch, the first step to encode the batch using the Batch::encode() method. The output bytes need to then be compressed prior to adding them to the ChannelOut.

note

The ChannelOut type also provides a method for adding the Batch itself, handling encoding and compression, but this method is not available yet.

Once compressed using the compress_brotli method, the compressed bytes can be added to a newly constructed ChannelOut. As long as the ChannelOut has ready_bytes(), Frames can be constructed using the ChannelOut::output_frame() method, specifying the maximum frame size.

Once Frames are returned from the ChannelOut, they can be Frame::encode into raw, serialized data ready to be batch-submitted to the data-availability layer.

Running this example:

  • Clone the examples repository: git clone git@github.com:alloy-rs/op-alloy.git
  • Run: cargo run --example batch_to_frames
//! An example encoding and decoding a [SingleBatch].
//!
//! This example demonstrates EIP-2718 encoding a [SingleBatch]
//! through a [ChannelOut] and into individual [Frame]s.
//!
//! Notice, the raw batch is first _encoded_.
//! Once encoded, it is compressed into raw data that the channel is constructed with.
//!
//! The [ChannelOut] then outputs frames individually using the maximum frame size,
//! in this case hardcoded to 100, to construct the frames.
//!
//! Finally, once [Frame]s are built from the [ChannelOut], they are encoded and ready
//! to be batch-submitted to the data availability layer.

#[cfg(feature = "std")]
fn main() {
    use alloy_primitives::BlockHash;
    use op_alloy_genesis::RollupConfig;
    use op_alloy_protocol::{Batch, ChannelId, ChannelOut, SingleBatch};

    // Use the example transaction
    let transactions = example_transactions();

    // Construct a basic `SingleBatch`
    let parent_hash = BlockHash::ZERO;
    let epoch_num = 1;
    let epoch_hash = BlockHash::ZERO;
    let timestamp = 1;
    let single_batch = SingleBatch { parent_hash, epoch_num, epoch_hash, timestamp, transactions };
    let batch = Batch::Single(single_batch);

    // Create a new channel.
    let id = ChannelId::default();
    let config = RollupConfig::default();
    let mut channel_out = ChannelOut::new(id, &config);

    // Add the compressed batch to the `ChannelOut`.
    channel_out.add_batch(batch).unwrap();

    // Output frames
    while channel_out.ready_bytes() > 0 {
        let frame = channel_out.output_frame(100).expect("outputs frame");
        println!("Frame: {}", alloy_primitives::hex::encode(frame.encode()));
        if channel_out.ready_bytes() <= 100 {
            channel_out.close();
        }
    }

    assert!(channel_out.closed);
    println!("Successfully encoded Batch to frames");
}

#[cfg(feature = "std")]
fn example_transactions() -> Vec<alloy_primitives::Bytes> {
    use alloy_consensus::{SignableTransaction, TxEip1559};
    use alloy_eips::eip2718::{Decodable2718, Encodable2718};
    use alloy_primitives::{Address, PrimitiveSignature, U256};
    use op_alloy_consensus::OpTxEnvelope;

    let mut transactions = Vec::new();

    // First Transaction in the batch.
    let tx = TxEip1559 {
        chain_id: 10u64,
        nonce: 2,
        max_fee_per_gas: 3,
        max_priority_fee_per_gas: 4,
        gas_limit: 5,
        to: Address::left_padding_from(&[6]).into(),
        value: U256::from(7_u64),
        input: vec![8].into(),
        access_list: Default::default(),
    };
    let sig = PrimitiveSignature::test_signature();
    let tx_signed = tx.into_signed(sig);
    let envelope: OpTxEnvelope = tx_signed.into();
    let encoded = envelope.encoded_2718();
    transactions.push(encoded.clone().into());
    let mut slice = encoded.as_slice();
    let decoded = OpTxEnvelope::decode_2718(&mut slice).unwrap();
    assert!(matches!(decoded, OpTxEnvelope::Eip1559(_)));

    // Second transaction in the batch.
    let tx = TxEip1559 {
        chain_id: 10u64,
        nonce: 2,
        max_fee_per_gas: 3,
        max_priority_fee_per_gas: 4,
        gas_limit: 5,
        to: Address::left_padding_from(&[7]).into(),
        value: U256::from(7_u64),
        input: vec![8].into(),
        access_list: Default::default(),
    };
    let sig = PrimitiveSignature::test_signature();
    let tx_signed = tx.into_signed(sig);
    let envelope: OpTxEnvelope = tx_signed.into();
    let encoded = envelope.encoded_2718();
    transactions.push(encoded.clone().into());
    let mut slice = encoded.as_slice();
    let decoded = OpTxEnvelope::decode_2718(&mut slice).unwrap();
    assert!(matches!(decoded, OpTxEnvelope::Eip1559(_)));

    transactions
}

#[cfg(not(feature = "std"))]
fn main() {
    /* not implemented for no_std */
}