Transform Frames into a Batch

note

This example performs the reverse transformation as the batch-to-frames 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 individual Frames into a Batch type.

This example walks through transforming Frames into the Batch types.

Walkthrough

The high level transformation is the following.

raw bytes[] -> frames[] -> channel -> decompressed channel data -> Batch

Given the raw, batch-submitted frame data as bytes (read in with the hex! macro), the first step is to decode the frame data into Frames using Frame::decode. Once all the Frames are decoded, the Channel can be constructed using the ChannelId of the first frame.

note

Frames may also be added to a Channel once decoded with the Channel::add_frame method.

When the Channel is Channel::is_ready(), the frame data can taken from the Channel using Channel::frame_data(). This data is represented as Bytes and needs to be decompressed using the respective compression algorithm depending on which hardforks are activated (using the RollupConfig). For the sake of this example, brotli is used (which was activated in the Fjord hardfork). Decompressed brotli bytes can then be passed right into Batch::decode to wind up with the example's desired Batch.

Running this example:

  • Clone the examples repository: git clone git@github.com:alloy-rs/op-alloy.git
  • Run: cargo run --example frames_to_batch
//! This example decodes raw [Frame]s and reads them into a [Channel] and into a [SingleBatch].

use alloy_consensus::{SignableTransaction, TxEip1559};
use alloy_eips::eip2718::{Decodable2718, Encodable2718};
use alloy_primitives::{hex, Address, BlockHash, Bytes, PrimitiveSignature, U256};
use op_alloy_consensus::OpTxEnvelope;
use op_alloy_genesis::RollupConfig;
use op_alloy_protocol::{decompress_brotli, Batch, BlockInfo, Channel, Frame, SingleBatch};

fn main() {
    // Raw frame data taken from the `encode_channel` example.
    let first_frame = hex!("60d54f49b71978b1b09288af847b11d200000000004d1b1301f82f0f6c3734f4821cd090ef3979d71a98e7e483b1dccdd525024c0ef16f425c7b4976a7acc0c94a0514b72c096d4dcc52f0b22dae193c70c86d0790a304a08152c8250031d091063ea000");
    let second_frame = hex!("60d54f49b71978b1b09288af847b11d2000100000046b00d00005082edde7ccf05bded2004462b5e80e1c42cd08e307f5baac723b22864cc6cd01ddde84efc7c018d7ada56c2fa8e3c5bedd494c3a7a884439d5771afcecaf196cb3801");

    // Decode the raw frames.
    let decoded_first = Frame::decode(&first_frame).expect("decodes frame").1;
    let decoded_second = Frame::decode(&second_frame).expect("decodes frame").1;

    // Create a channel.
    let id = decoded_first.id;
    let open_block = BlockInfo::default();
    let mut channel = Channel::new(id, open_block);

    // Add the frames to the channel.
    let l1_inclusion_block = BlockInfo::default();
    channel.add_frame(decoded_first, l1_inclusion_block).expect("adds frame");
    channel.add_frame(decoded_second, l1_inclusion_block).expect("adds frame");

    // Get the frame data from the channel.
    let frame_data = channel.frame_data().expect("some frame data");
    println!("Frame data: {}", hex::encode(&frame_data));

    // Decompress the frame data with brotli.
    let config = RollupConfig::default();
    let max = config.max_rlp_bytes_per_channel(open_block.timestamp) as usize;
    let decompressed = decompress_brotli(&frame_data, max).expect("decompresses brotli");
    println!("Decompressed frame data: {}", hex::encode(&decompressed));

    // Decode the single batch from the decompressed data.
    let batch = Batch::decode(&mut decompressed.as_slice(), &config).expect("batch decodes");
    assert_eq!(
        batch,
        Batch::Single(SingleBatch {
            parent_hash: BlockHash::ZERO,
            epoch_num: 1,
            epoch_hash: BlockHash::ZERO,
            timestamp: 1,
            transactions: example_transactions(),
        })
    );

    println!("Successfully decoded frames into a Batch");
}

fn example_transactions() -> Vec<Bytes> {
    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
}