Skip to content

Commit

Permalink
fix(derive): SpanBatch element limit + channel RLP size limit (#692)
Browse files Browse the repository at this point in the history
* fix(derive): SpanBatch element limit + channel RLP size limit

* remove unused fn

* channel reader fjord test
  • Loading branch information
clabby authored Oct 15, 2024
1 parent 3e21875 commit 4e7f4d1
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 287 deletions.
5 changes: 2 additions & 3 deletions crates/derive/src/batch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ pub use span_batch::{
RawSpanBatch, SpanBatch, SpanBatchBits, SpanBatchEip1559TransactionData,
SpanBatchEip2930TransactionData, SpanBatchElement, SpanBatchError,
SpanBatchLegacyTransactionData, SpanBatchPayload, SpanBatchPrefix, SpanBatchSignature,
SpanBatchTransactionData, SpanBatchTransactions, SpanDecodingError, FJORD_MAX_SPAN_BATCH_BYTES,
MAX_SPAN_BATCH_BYTES,
SpanBatchTransactionData, SpanBatchTransactions, SpanDecodingError, MAX_SPAN_BATCH_ELEMENTS,
};

mod single_batch;
Expand Down Expand Up @@ -102,7 +101,7 @@ impl Batch {
Ok(Self::Single(single_batch))
}
BatchType::Span => {
let mut raw_span_batch = RawSpanBatch::decode(r, cfg)?;
let mut raw_span_batch = RawSpanBatch::decode(r)?;
let span_batch = raw_span_batch
.derive(cfg.block_time, cfg.genesis.l2_time, cfg.l2_chain_id)
.map_err(PipelineEncodingError::SpanBatchError)?;
Expand Down
48 changes: 7 additions & 41 deletions crates/derive/src/batch/span_batch/bits.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Module for working with span batch bits.

use super::{errors::SpanBatchError, FJORD_MAX_SPAN_BATCH_BYTES, MAX_SPAN_BATCH_BYTES};
use super::errors::SpanBatchError;
use alloc::{vec, vec::Vec};
use alloy_rlp::Buf;
use core::cmp::Ordering;
Expand All @@ -16,29 +16,11 @@ impl AsRef<[u8]> for SpanBatchBits {
}

impl SpanBatchBits {
/// Returns the max amount of bytes that can be stored in the bitlist.
pub const fn max_bytes(is_fjord_active: bool) -> usize {
if is_fjord_active {
FJORD_MAX_SPAN_BATCH_BYTES as usize
} else {
MAX_SPAN_BATCH_BYTES as usize
}
}

/// Decodes a standard span-batch bitlist from a reader.
/// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8
/// bits. The encoded bitlist cannot be longer than [MAX_SPAN_BATCH_BYTES].
pub fn decode(
b: &mut &[u8],
bit_length: usize,
is_fjord_active: bool,
) -> Result<Self, SpanBatchError> {
/// bits. The encoded bitlist cannot be longer than `bit_length`.
pub fn decode(b: &mut &[u8], bit_length: usize) -> Result<Self, SpanBatchError> {
let buffer_len = bit_length / 8 + if bit_length % 8 != 0 { 1 } else { 0 };
let max_bytes = Self::max_bytes(is_fjord_active);
if buffer_len > max_bytes {
return Err(SpanBatchError::TooBigSpanBatchSize);
}

let bits = if b.len() < buffer_len {
let mut bits = vec![0; buffer_len];
bits[..b.len()].copy_from_slice(b);
Expand All @@ -60,25 +42,15 @@ impl SpanBatchBits {

/// Encodes a standard span-batch bitlist.
/// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8
/// bits. The encoded bitlist cannot be longer than [MAX_SPAN_BATCH_BYTES] or
/// [FJORD_MAX_SPAN_BATCH_BYTES] if fjord is active.
pub fn encode(
w: &mut Vec<u8>,
bit_length: usize,
bits: &Self,
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
/// bits. The encoded bitlist cannot be longer than `bit_length`
pub fn encode(w: &mut Vec<u8>, bit_length: usize, bits: &Self) -> Result<(), SpanBatchError> {
if bits.bit_len() > bit_length {
return Err(SpanBatchError::BitfieldTooLong);
}

// Round up, ensure enough bytes when number of bits is not a multiple of 8.
// Alternative of (L+7)/8 is not overflow-safe.
let buf_len = bit_length / 8 + if bit_length % 8 != 0 { 1 } else { 0 };
let max_bytes = Self::max_bytes(is_fjord_active);
if buf_len > max_bytes {
return Err(SpanBatchError::TooBigSpanBatchSize);
}
let mut buf = vec![0; buf_len];
buf[buf_len - bits.0.len()..].copy_from_slice(bits.as_ref());
w.extend_from_slice(&buf);
Expand Down Expand Up @@ -174,19 +146,13 @@ mod test {
use super::*;
use proptest::{collection::vec, prelude::any, proptest};

#[test]
fn test_bitlist_max_bytes() {
assert_eq!(SpanBatchBits::max_bytes(false), MAX_SPAN_BATCH_BYTES as usize);
assert_eq!(SpanBatchBits::max_bytes(true), FJORD_MAX_SPAN_BATCH_BYTES as usize);
}

proptest! {
#[test]
fn test_encode_decode_roundtrip_span_bitlist(vec in vec(any::<u8>(), 0..5096)) {
let bits = SpanBatchBits(vec);
assert_eq!(SpanBatchBits::decode(&mut bits.as_ref(), bits.0.len() * 8, false).unwrap(), bits);
assert_eq!(SpanBatchBits::decode(&mut bits.as_ref(), bits.0.len() * 8).unwrap(), bits);
let mut encoded = Vec::new();
SpanBatchBits::encode(&mut encoded, bits.0.len() * 8, &bits, false).unwrap();
SpanBatchBits::encode(&mut encoded, bits.0.len() * 8, &bits).unwrap();
assert_eq!(encoded, bits.0);
}

Expand Down
16 changes: 3 additions & 13 deletions crates/derive/src/batch/span_batch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,9 @@
//! txs = contract_creation_bits ++ y_parity_bits ++ tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits
//! ```

/// [MAX_SPAN_BATCH_BYTES] is the maximum amount of bytes that will be needed
/// to decode every span batch field.
///
/// This value cannot be larger than MaxRLPBytesPerChannel because single batch cannot be larger
/// than channel size.
pub const MAX_SPAN_BATCH_BYTES: u64 = op_alloy_protocol::MAX_RLP_BYTES_PER_CHANNEL;

/// [FJORD_MAX_SPAN_BATCH_BYTES] is the maximum amount of bytes that will be needed
/// to decode every span batch field after the Fjord Hardfork.
///
/// This value cannot be larger than MaxRLPBytesPerChannel because single batch
/// cannot be larger than channel size.
pub const FJORD_MAX_SPAN_BATCH_BYTES: u64 = op_alloy_protocol::FJORD_MAX_RLP_BYTES_PER_CHANNEL;
/// MAX_SPAN_BATCH_ELEMENTS is the maximum number of blocks, transactions in total,
/// or transaction per block allowed in a span batch.
pub const MAX_SPAN_BATCH_ELEMENTS: u64 = 10_000_000;

mod batch;
pub use batch::SpanBatch;
Expand Down
138 changes: 39 additions & 99 deletions crates/derive/src/batch/span_batch/payload.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Raw Span Batch Payload

use super::{FJORD_MAX_SPAN_BATCH_BYTES, MAX_SPAN_BATCH_BYTES};
use super::MAX_SPAN_BATCH_ELEMENTS;
use crate::batch::{SpanBatchBits, SpanBatchError, SpanBatchTransactions, SpanDecodingError};
use alloc::vec::Vec;

Expand All @@ -20,58 +20,40 @@ pub struct SpanBatchPayload {

impl SpanBatchPayload {
/// Decodes a [SpanBatchPayload] from a reader.
pub fn decode_payload(r: &mut &[u8], is_fjord_active: bool) -> Result<Self, SpanBatchError> {
pub fn decode_payload(r: &mut &[u8]) -> Result<Self, SpanBatchError> {
let mut payload = Self::default();
payload.decode_block_count(r, is_fjord_active)?;
payload.decode_origin_bits(r, is_fjord_active)?;
payload.decode_block_tx_counts(r, is_fjord_active)?;
payload.decode_txs(r, is_fjord_active)?;
payload.decode_block_count(r)?;
payload.decode_origin_bits(r)?;
payload.decode_block_tx_counts(r)?;
payload.decode_txs(r)?;
Ok(payload)
}

/// Encodes a [SpanBatchPayload] into a writer.
pub fn encode_payload(
&self,
w: &mut Vec<u8>,
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
pub fn encode_payload(&self, w: &mut Vec<u8>) -> Result<(), SpanBatchError> {
self.encode_block_count(w);
self.encode_origin_bits(w, is_fjord_active)?;
self.encode_origin_bits(w)?;
self.encode_block_tx_counts(w);
self.encode_txs(w, is_fjord_active)
self.encode_txs(w)
}

/// Decodes the origin bits from a reader.
pub fn decode_origin_bits(
&mut self,
r: &mut &[u8],
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
self.origin_bits = SpanBatchBits::decode(r, self.block_count as usize, is_fjord_active)?;
Ok(())
}

/// Returns the max span batch size based on the Fjord hardfork.
pub const fn max_span_batch_size(&self, is_fjord_active: bool) -> usize {
if is_fjord_active {
FJORD_MAX_SPAN_BATCH_BYTES as usize
} else {
MAX_SPAN_BATCH_BYTES as usize
pub fn decode_origin_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> {
if self.block_count > MAX_SPAN_BATCH_ELEMENTS {
return Err(SpanBatchError::TooBigSpanBatchSize);
}

self.origin_bits = SpanBatchBits::decode(r, self.block_count as usize)?;
Ok(())
}

/// Decode a block count from a reader.
pub fn decode_block_count(
&mut self,
r: &mut &[u8],
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
pub fn decode_block_count(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> {
let (block_count, remaining) = unsigned_varint::decode::u64(r)
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::BlockCount))?;
// The number of transactions in a single L2 block cannot be greater than
// [MAX_SPAN_BATCH_BYTES] or [FJORD_MAX_SPAN_BATCH_BYTES] if Fjord is active.
let max_span_batch_size = self.max_span_batch_size(is_fjord_active);
if block_count as usize > max_span_batch_size {
// [MAX_SPAN_BATCH_ELEMENTS].
if block_count > MAX_SPAN_BATCH_ELEMENTS {
return Err(SpanBatchError::TooBigSpanBatchSize);
}
if block_count == 0 {
Expand All @@ -83,11 +65,7 @@ impl SpanBatchPayload {
}

/// Decode block transaction counts from a reader.
pub fn decode_block_tx_counts(
&mut self,
r: &mut &[u8],
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
pub fn decode_block_tx_counts(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> {
// Initially allocate the vec with the block count, to reduce re-allocations in the first
// few blocks.
let mut block_tx_counts = Vec::with_capacity(self.block_count as usize);
Expand All @@ -97,10 +75,8 @@ impl SpanBatchPayload {
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::BlockTxCounts))?;

// The number of transactions in a single L2 block cannot be greater than
// [MAX_SPAN_BATCH_BYTES] or [FJORD_MAX_SPAN_BATCH_BYTES] if Fjord is active.
// Every transaction will take at least a single byte.
let max_span_batch_size = self.max_span_batch_size(is_fjord_active);
if block_tx_count as usize > max_span_batch_size {
// [MAX_SPAN_BATCH_ELEMENTS].
if block_tx_count > MAX_SPAN_BATCH_ELEMENTS {
return Err(SpanBatchError::TooBigSpanBatchSize);
}
block_tx_counts.push(block_tx_count);
Expand All @@ -111,11 +87,7 @@ impl SpanBatchPayload {
}

/// Decode transactions from a reader.
pub fn decode_txs(
&mut self,
r: &mut &[u8],
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
pub fn decode_txs(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> {
if self.block_tx_counts.is_empty() {
return Err(SpanBatchError::EmptySpanBatch);
}
Expand All @@ -126,23 +98,18 @@ impl SpanBatchPayload {
})?;

// The total number of transactions in a span batch cannot be greater than
// [MAX_SPAN_BATCH_BYTES] or [FJORD_MAX_SPAN_BATCH_BYTES] if Fjord is active.
let max_span_batch_size = self.max_span_batch_size(is_fjord_active);
if total_block_tx_count as usize > max_span_batch_size {
// [MAX_SPAN_BATCH_ELEMENTS].
if total_block_tx_count > MAX_SPAN_BATCH_ELEMENTS {
return Err(SpanBatchError::TooBigSpanBatchSize);
}
self.txs.total_block_tx_count = total_block_tx_count;
self.txs.decode(r, is_fjord_active)?;
self.txs.decode(r)?;
Ok(())
}

/// Encode the origin bits into a writer.
pub fn encode_origin_bits(
&self,
w: &mut Vec<u8>,
is_fjord_active: bool,
) -> Result<(), SpanBatchError> {
SpanBatchBits::encode(w, self.block_count as usize, &self.origin_bits, is_fjord_active)
pub fn encode_origin_bits(&self, w: &mut Vec<u8>) -> Result<(), SpanBatchError> {
SpanBatchBits::encode(w, self.block_count as usize, &self.origin_bits)
}

/// Encode the block count into a writer.
Expand All @@ -161,8 +128,8 @@ impl SpanBatchPayload {
}

/// Encode the transactions into a writer.
pub fn encode_txs(&self, w: &mut Vec<u8>, is_fjord_active: bool) -> Result<(), SpanBatchError> {
self.txs.encode(w, is_fjord_active)
pub fn encode_txs(&self, w: &mut Vec<u8>) -> Result<(), SpanBatchError> {
self.txs.encode(w)
}
}

Expand All @@ -177,7 +144,7 @@ mod tests {
let encoded = vec![2; block_count / 8 + 1];
let mut payload =
SpanBatchPayload { block_count: block_count as u64, ..Default::default() };
payload.decode_origin_bits(&mut encoded.as_slice(), false).unwrap();
payload.decode_origin_bits(&mut encoded.as_slice()).unwrap();
assert_eq!(payload.origin_bits, SpanBatchBits(vec![2; block_count / 8 + 1]));
}

Expand All @@ -186,47 +153,27 @@ mod tests {
let mut u64_varint_buf = [0; 10];
let mut encoded = unsigned_varint::encode::u64(0, &mut u64_varint_buf);
let mut payload = SpanBatchPayload::default();
let err = payload.decode_block_count(&mut encoded, false).unwrap_err();
let err = payload.decode_block_count(&mut encoded).unwrap_err();
assert_eq!(err, SpanBatchError::EmptySpanBatch);
}

#[test]
fn test_decode_block_count_pre_fjord() {
let block_count = MAX_SPAN_BATCH_BYTES;
fn test_decode_block_count() {
let block_count = MAX_SPAN_BATCH_ELEMENTS;
let mut u64_varint_buf = [0; 10];
let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf);
let mut payload = SpanBatchPayload::default();
payload.decode_block_count(&mut encoded, false).unwrap();
payload.decode_block_count(&mut encoded).unwrap();
assert_eq!(payload.block_count, block_count);
}

#[test]
fn test_decode_block_count_pre_fjord_errors() {
let block_count = MAX_SPAN_BATCH_BYTES + 1;
fn test_decode_block_count_errors() {
let block_count = MAX_SPAN_BATCH_ELEMENTS + 1;
let mut u64_varint_buf = [0; 10];
let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf);
let mut payload = SpanBatchPayload::default();
let err = payload.decode_block_count(&mut encoded, false).unwrap_err();
assert_eq!(err, SpanBatchError::TooBigSpanBatchSize);
}

#[test]
fn test_decode_block_count_post_fjord() {
let block_count = FJORD_MAX_SPAN_BATCH_BYTES;
let mut u64_varint_buf = [0; 10];
let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf);
let mut payload = SpanBatchPayload::default();
payload.decode_block_count(&mut encoded, true).unwrap();
assert_eq!(payload.block_count, block_count);
}

#[test]
fn test_decode_block_count_post_fjord_errors() {
let block_count = FJORD_MAX_SPAN_BATCH_BYTES + 1;
let mut u64_varint_buf = [0; 10];
let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf);
let mut payload = SpanBatchPayload::default();
let err = payload.decode_block_count(&mut encoded, true).unwrap_err();
let err = payload.decode_block_count(&mut encoded).unwrap_err();
assert_eq!(err, SpanBatchError::TooBigSpanBatchSize);
}

Expand All @@ -236,21 +183,14 @@ mod tests {
let mut u64_varint_buf = [0; 10];
let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf);
let mut payload = SpanBatchPayload::default();
payload.decode_block_count(&mut encoded, false).unwrap();
payload.decode_block_count(&mut encoded).unwrap();
let mut r: Vec<u8> = Vec::new();
for _ in 0..2 {
let mut buf = [0u8; 10];
let encoded = unsigned_varint::encode::u64(2, &mut buf);
r.append(&mut encoded.to_vec());
}
payload.decode_block_tx_counts(&mut r.as_slice(), false).unwrap();
payload.decode_block_tx_counts(&mut r.as_slice()).unwrap();
assert_eq!(payload.block_tx_counts, vec![2, 2]);
}

#[test]
fn test_max_span_batch_size() {
let payload = SpanBatchPayload::default();
assert_eq!(payload.max_span_batch_size(false), MAX_SPAN_BATCH_BYTES as usize);
assert_eq!(payload.max_span_batch_size(true), FJORD_MAX_SPAN_BATCH_BYTES as usize);
}
}
Loading

0 comments on commit 4e7f4d1

Please sign in to comment.