Skip to content

Commit

Permalink
feat(derive): SpanBatch type implementation WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Apr 3, 2024
1 parent bd6792e commit b82d076
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 31 deletions.
4 changes: 2 additions & 2 deletions crates/derive/src/types/batch/single_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use alloy_primitives::BlockHash;
use alloy_rlp::{RlpDecodable, RlpEncodable};

/// Represents a single batch: a single encoded L2 block
#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
#[derive(Debug, Default, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
pub struct SingleBatch {
/// Block hash of the previous L2 block
/// Block hash of the previous L2 block. `B256::ZERO` if it has not been set by the Batch Queue.
pub parent_hash: BlockHash,
/// The batch epoch number. Same as the first L1 block number in the epoch.
pub epoch_num: u64,
Expand Down
108 changes: 99 additions & 9 deletions crates/derive/src/types/batch/span_batch/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@
#![allow(unused)]

use super::{SpanBatchError, SpanBatchTransactions};
use crate::types::{
RawSpanBatch, SpanBatchBits, SpanBatchElement, SpanBatchPayload, SpanBatchPrefix,
block::L2BlockInfo, BlockInfo, RawSpanBatch, SingleBatch, SpanBatchBits, SpanBatchElement,
SpanBatchPayload, SpanBatchPrefix,
};
use alloc::{vec, vec::Vec};
use alloy_primitives::FixedBytes;

use super::SpanBatchError;

/// The span batch contains the input to build a span of L2 blocks in derived form.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SpanBatch {
/// First 20 bytes of the first block's parent hash
pub parent_check: FixedBytes<20>,
/// First 20 bytes of the last block's L1 origin hash
pub l1_origin_check: FixedBytes<20>,
/// Genesis block timestamp
pub genesis_timestamp: u64,
/// Chain ID
pub chain_id: u64,
/// List of block input in derived form
pub batches: Vec<SpanBatchElement>,
/// Caching - origin bits
pub origin_bits: SpanBatchBits,
/// Caching - block tx counts
pub block_tx_counts: Vec<u64>,
/// Caching - span batch txs
pub txs: SpanBatchTransactions,
}

impl SpanBatch {
Expand All @@ -41,8 +51,6 @@ impl SpanBatch {
let span_start = self.batches.first().ok_or(SpanBatchError::EmptySpanBatch)?;
let span_end = self.batches.last().ok_or(SpanBatchError::EmptySpanBatch)?;

// TODO: Need to expand the [SpanBatch] type, as implemented in `op-node`. It should have extra data, incl.
// the origin bits, block tx counts, and span batch txs.
Ok(RawSpanBatch {
prefix: SpanBatchPrefix {
rel_timestamp: span_start.timestamp - genesis_timestamp,
Expand All @@ -52,10 +60,92 @@ impl SpanBatch {
},
payload: SpanBatchPayload {
block_count: self.batches.len() as u64,
origin_bits: todo!(),
block_tx_counts: todo!(),
txs: todo!(),
origin_bits: self.origin_bits.clone(),
block_tx_counts: self.block_tx_counts.clone(),
txs: self.txs.clone(),
},
})
}

/// Converts all [SpanBatchElement]s after the L2 safe head to [SingleBatch]es. The resulting [SingleBatch]es do
/// not contain a parent hash, as it is populated by the Batch Queue stage.
pub fn get_singular_batches(
&self,
l1_origins: Vec<BlockInfo>,
l2_safe_head: L2BlockInfo,
) -> Result<Vec<SingleBatch>, SpanBatchError> {
let mut single_batches = Vec::new();
let mut origin_index = 0;
for batch in &self.batches {
if batch.timestamp <= l2_safe_head.block_info.timestamp {
continue;
}
let origin_epoch_hash = l1_origins[origin_index..l1_origins.len()]
.iter()
.enumerate()
.find(|(i, origin)| origin.timestamp == batch.timestamp)
.map(|(i, origin)| {
origin_index = i;
origin.hash
})
.ok_or(SpanBatchError::MissingL1Origin)?;
let mut single_batch = SingleBatch {
epoch_num: batch.epoch_num,
epoch_hash: origin_epoch_hash,
timestamp: batch.timestamp,
transactions: batch.transactions.clone(),
..Default::default()
};
single_batches.push(single_batch);
}
Ok(single_batches)
}

/// Append a [SingleBatch] to the [SpanBatch]. Updates the L1 origin check if need be.
pub fn append_singular_batch(
&mut self,
singular_batch: SingleBatch,
seq_num: u64,
) -> Result<(), SpanBatchError> {
// If the new element is not ordered with respect to the last element, panic.
if !self.batches.is_empty() && self.peek(0).timestamp > singular_batch.timestamp {
panic!("Batch is not ordered");
}

let SingleBatch {
epoch_hash,
parent_hash,
..
} = singular_batch;

// Always append the new batch and set the L1 origin check.
self.batches.push(singular_batch.into());
// Always update the L1 origin check.
self.l1_origin_check = epoch_hash[..20].try_into().expect("Sub-slice cannot fail");

let epoch_bit = if self.batches.len() == 1 {
// If there is only one batch, initialize the parent check and set the epoch bit based on the sequence number.
self.parent_check = parent_hash[..20].try_into().expect("Sub-slice cannot fail");
seq_num == 0
} else {
// If there is more than one batch, set the epoch bit based on the last two batches.
self.peek(1).epoch_num < self.peek(0).epoch_num
};

// Set the respective bit in the origin bits.
self.origin_bits.set_bit(self.batches.len() - 1, epoch_bit);

let new_txs = self.peek(0).transactions.clone();

// Update the block tx counts cache with the latest batch's transaction count.
self.block_tx_counts.push(new_txs.len() as u64);

// Add the new transactions to the transaction cache.
self.txs.add_txs(new_txs, self.chain_id)
}

/// Peek at the `n`th-to-last last element in the batch.
fn peek(&self, n: usize) -> &SpanBatchElement {
&self.batches[self.batches.len() - 1 - n]
}
}
2 changes: 1 addition & 1 deletion crates/derive/src/types/batch/span_batch/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl SpanBatchBits {

/// Calculates the bit length of the [SpanBatchBits] bitfield.
pub fn bit_len(&self) -> usize {
if let Some((ref top_word, rest)) = self.0.split_last() {
if let Some((top_word, rest)) = self.0.split_last() {
// Calculate bit length. Rust's leading_zeros counts zeros from the MSB, so subtract from total bits.
let significant_bits = 8 - top_word.leading_zeros() as usize;

Expand Down
12 changes: 2 additions & 10 deletions crates/derive/src/types/batch/span_batch/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ impl SpanBatchBuilder {
SpanBatchBuilder {
genesis_timestamp,
chain_id,
span_batch: SpanBatch {
parent_check: FixedBytes::<20>::default(),
l1_origin_check: FixedBytes::<20>::default(),
batches: Vec::new(),
},
span_batch: SpanBatch::default(),
origin_changed_bit: 0,
}
}
Expand All @@ -44,11 +40,7 @@ impl SpanBatchBuilder {

/// Resets the span batch builder.
pub fn reset(&mut self) {
self.span_batch = SpanBatch {
parent_check: FixedBytes::<20>::default(),
l1_origin_check: FixedBytes::<20>::default(),
batches: Vec::new(),
};
self.span_batch = SpanBatch::default();
self.origin_changed_bit = 0;
}

Expand Down
3 changes: 3 additions & 0 deletions crates/derive/src/types/batch/span_batch/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub enum SpanBatchError {
InvalidBitSlice,
/// Empty Span Batch
EmptySpanBatch,
/// Missing L1 origin
MissingL1Origin,
/// Encoding errors
Encoding(EncodingError),
/// Decoding errors
Expand All @@ -29,6 +31,7 @@ impl Display for SpanBatchError {
"Failed to set [alloy_primitives::U256] from big-endian slice"
),
SpanBatchError::EmptySpanBatch => write!(f, "Empty Span Batch"),
SpanBatchError::MissingL1Origin => write!(f, "Missing L1 origin"),
SpanBatchError::Encoding(e) => write!(f, "Encoding error: {:?}", e),
SpanBatchError::Decoding(e) => write!(f, "Decoding error: {:?}", e),
}
Expand Down
14 changes: 8 additions & 6 deletions crates/derive/src/types/batch/span_batch/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ pub struct RawSpanBatch {
}

impl RawSpanBatch {
/// Returns the batch type
pub fn get_batch_type(&self) -> u8 {
SPAN_BATCH_TYPE
}

/// Encodes the [RawSpanBatch] into a writer.
pub fn encode(&self, w: &mut Vec<u8>) -> Result<(), SpanBatchError> {
self.prefix.encode_prefix(w);
Expand All @@ -32,7 +37,8 @@ impl RawSpanBatch {
Ok(Self { prefix, payload })
}

/// Converts a [RawSpanBatch] into a [SpanBatch], which has a list of [SpanBatchElement]s.
/// Converts a [RawSpanBatch] into a [SpanBatch], which has a list of [SpanBatchElement]s. Thos function does not
/// populate the [SpanBatch] with chain configuration data, which is required for making payload attributes.
pub fn derive(
&mut self,
block_time: u64,
Expand Down Expand Up @@ -88,13 +94,9 @@ impl RawSpanBatch {
parent_check: self.prefix.parent_check,
l1_origin_check: self.prefix.l1_origin_check,
batches,
..Default::default()
})
}

/// Returns the batch type
pub fn get_batch_type(&self) -> u8 {
SPAN_BATCH_TYPE
}
}

#[cfg(test)]
Expand Down
10 changes: 7 additions & 3 deletions crates/derive/src/types/batch/span_batch/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use super::{convert_v_to_y_parity, read_tx_data};
use super::{
SpanBatchBits, SpanBatchError, SpanBatchSignature, SpanBatchTransactionData, SpanDecodingError,
};
use crate::types::{Transaction, TxEnvelope, TxKind, TxType};
use crate::types::{RawTransaction, Transaction, TxEnvelope, TxKind, TxType};

/// This struct contains the decoded information for transactions in a span batch.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -319,12 +319,16 @@ impl SpanBatchTransactions {
}

/// Add raw transactions into the [SpanBatchTransactions].
pub fn add_txs(&mut self, txs: Vec<Vec<u8>>, _chain_id: u64) -> Result<(), SpanBatchError> {
pub fn add_txs(
&mut self,
txs: Vec<RawTransaction>,
_chain_id: u64,
) -> Result<(), SpanBatchError> {
let total_block_tx_count = txs.len() as u64;
let offset = self.total_block_tx_count;

for i in 0..total_block_tx_count {
let tx_enveloped = TxEnvelope::decode(&mut txs[i as usize].as_slice())
let tx_enveloped = TxEnvelope::decode(&mut txs[i as usize].as_ref())
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;
let span_batch_tx = SpanBatchTransactionData::try_from(&tx_enveloped)?;

Expand Down
23 changes: 23 additions & 0 deletions crates/derive/src/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,29 @@ impl BlockInfo {
}
}

/// L2 Block Header Info
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct L2BlockInfo {
/// The base [BlockInfo]
pub block_info: BlockInfo,
/// The L1 origin [BlockId]
pub l1_origin: BlockId,
/// The sequence number of the L2 block
pub seq_num: u64,
}

impl L2BlockInfo {
/// Instantiates a new [L2BlockInfo].
pub fn new(block_info: BlockInfo, l1_origin: BlockId, seq_num: u64) -> Self {
Self {
block_info,
l1_origin,
seq_num,
}
}
}

/// A Block Identifier
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand Down
6 changes: 6 additions & 0 deletions crates/derive/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ impl Decodable for RawTransaction {
}
}

impl AsRef<[u8]> for RawTransaction {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

/// A single L2 block derived from a batch.
#[derive(Debug, Clone)]
pub struct BlockInput {
Expand Down

0 comments on commit b82d076

Please sign in to comment.