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 Frame
s.
This example walks through transforming a Batch
into Frame
s.
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()
,
Frame
s can be constructed using the
ChannelOut::output_frame()
method, specifying the maximum
frame size.
Once Frame
s 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 */ }