use alloc::vec::Vec;
use alloy_primitives::{Bytes, map::HashMap};
use crate::{BlockInfo, Frame};
pub const CHANNEL_ID_LENGTH: usize = 16;
pub type ChannelId = [u8; CHANNEL_ID_LENGTH];
pub const MAX_RLP_BYTES_PER_CHANNEL: u64 = 10_000_000;
pub const FJORD_MAX_RLP_BYTES_PER_CHANNEL: u64 = 100_000_000;
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChannelError {
#[error("Frame id does not match channel id")]
FrameIdMismatch,
#[error("Channel is closed")]
ChannelClosed,
#[error("Frame number {0} already exists")]
FrameNumberExists(usize),
#[error("Frame number {0} is beyond end frame")]
FrameBeyondEndFrame(usize),
}
#[derive(Debug, Clone, Default)]
pub struct Channel {
id: ChannelId,
open_block: BlockInfo,
estimated_size: usize,
closed: bool,
highest_frame_number: u16,
last_frame_number: u16,
inputs: HashMap<u16, Frame>,
highest_l1_inclusion_block: BlockInfo,
}
impl Channel {
pub fn new(id: ChannelId, open_block: BlockInfo) -> Self {
Self { id, open_block, inputs: HashMap::default(), ..Default::default() }
}
pub const fn id(&self) -> ChannelId {
self.id
}
pub fn len(&self) -> usize {
self.inputs.len()
}
pub fn is_empty(&self) -> bool {
self.inputs.is_empty()
}
pub fn add_frame(
&mut self,
frame: Frame,
l1_inclusion_block: BlockInfo,
) -> Result<(), ChannelError> {
if frame.id != self.id {
return Err(ChannelError::FrameIdMismatch);
}
if frame.is_last && self.closed {
return Err(ChannelError::ChannelClosed);
}
if self.inputs.contains_key(&frame.number) {
return Err(ChannelError::FrameNumberExists(frame.number as usize));
}
if self.closed && frame.number >= self.last_frame_number {
return Err(ChannelError::FrameBeyondEndFrame(frame.number as usize));
}
if frame.is_last {
self.last_frame_number = frame.number;
self.closed = true;
if self.last_frame_number < self.highest_frame_number {
self.inputs.retain(|id, frame| {
self.estimated_size -= frame.size();
*id < self.last_frame_number
});
self.highest_frame_number = self.last_frame_number;
}
}
if frame.number > self.highest_frame_number {
self.highest_frame_number = frame.number;
}
if self.highest_l1_inclusion_block.number < l1_inclusion_block.number {
self.highest_l1_inclusion_block = l1_inclusion_block;
}
self.estimated_size += frame.size();
self.inputs.insert(frame.number, frame);
Ok(())
}
pub const fn open_block_number(&self) -> u64 {
self.open_block.number
}
pub const fn size(&self) -> usize {
self.estimated_size
}
pub fn is_ready(&self) -> bool {
if !self.closed {
return false;
}
if self.inputs.len() != (self.last_frame_number + 1) as usize {
return false;
}
for i in 0..=self.last_frame_number {
if !self.inputs.contains_key(&i) {
return false;
}
}
true
}
pub fn frame_data(&self) -> Option<Bytes> {
if self.is_empty() {
return None;
}
let mut data = Vec::with_capacity(self.size());
(0..=self.last_frame_number).try_for_each(|i| {
let frame = self.inputs.get(&i)?;
data.extend_from_slice(&frame.data);
Some(())
})?;
Some(data.into())
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::{
string::{String, ToString},
vec,
};
struct FrameValidityTestCase {
#[allow(dead_code)]
name: String,
frames: Vec<Frame>,
should_error: Vec<bool>,
sizes: Vec<u64>,
frame_data: Option<Bytes>,
}
fn run_frame_validity_test(test_case: FrameValidityTestCase) {
let id = [0xFF; 16];
let block = BlockInfo::default();
let mut channel = Channel::new(id, block);
if test_case.frames.len() != test_case.should_error.len() ||
test_case.frames.len() != test_case.sizes.len()
{
panic!("Test case length mismatch");
}
for (i, frame) in test_case.frames.iter().enumerate() {
let result = channel.add_frame(frame.clone(), block);
if test_case.should_error[i] {
assert!(result.is_err());
} else {
assert!(result.is_ok());
}
assert_eq!(channel.size(), test_case.sizes[i] as usize);
}
if test_case.frame_data.is_some() {
assert_eq!(channel.frame_data().unwrap(), test_case.frame_data.unwrap());
}
}
#[test]
fn test_channel_accessors() {
let id = [0xFF; 16];
let block = BlockInfo { number: 42, timestamp: 0, ..Default::default() };
let channel = Channel::new(id, block);
assert_eq!(channel.id(), id);
assert_eq!(channel.open_block_number(), block.number);
assert_eq!(channel.size(), 0);
assert_eq!(channel.len(), 0);
assert!(channel.is_empty());
assert!(!channel.is_ready());
}
#[test]
fn test_frame_validity() {
let id = [0xFF; 16];
let test_cases = [
FrameValidityTestCase {
name: "wrong channel".to_string(),
frames: vec![Frame { id: [0xEE; 16], ..Default::default() }],
should_error: vec![true],
sizes: vec![0],
frame_data: None,
},
FrameValidityTestCase {
name: "double close".to_string(),
frames: vec![
Frame { id, is_last: true, number: 2, data: b"four".to_vec() },
Frame { id, is_last: true, number: 1, ..Default::default() },
],
should_error: vec![false, true],
sizes: vec![204, 204],
frame_data: None,
},
FrameValidityTestCase {
name: "duplicate frame".to_string(),
frames: vec![
Frame { id, number: 2, data: b"four".to_vec(), ..Default::default() },
Frame { id, number: 2, data: b"seven".to_vec(), ..Default::default() },
],
should_error: vec![false, true],
sizes: vec![204, 204],
frame_data: None,
},
FrameValidityTestCase {
name: "duplicate closing frames".to_string(),
frames: vec![
Frame { id, number: 2, is_last: true, data: b"four".to_vec() },
Frame { id, number: 2, is_last: true, data: b"seven".to_vec() },
],
should_error: vec![false, true],
sizes: vec![204, 204],
frame_data: None,
},
FrameValidityTestCase {
name: "frame past closing".to_string(),
frames: vec![
Frame { id, number: 2, is_last: true, data: b"four".to_vec() },
Frame { id, number: 10, data: b"seven".to_vec(), ..Default::default() },
],
should_error: vec![false, true],
sizes: vec![204, 204],
frame_data: None,
},
FrameValidityTestCase {
name: "prune after close frame".to_string(),
frames: vec![
Frame { id, number: 0, is_last: false, data: b"seven".to_vec() },
Frame { id, number: 1, is_last: true, data: b"four".to_vec() },
],
should_error: vec![false, false],
sizes: vec![205, 409],
frame_data: Some(b"sevenfour".to_vec().into()),
},
FrameValidityTestCase {
name: "multiple valid frames, no data".to_string(),
frames: vec![
Frame { id, number: 1, data: b"seven__".to_vec(), ..Default::default() },
Frame { id, number: 2, data: b"four".to_vec(), ..Default::default() },
],
should_error: vec![false, false],
sizes: vec![207, 411],
frame_data: None,
},
FrameValidityTestCase {
name: "multiple valid frames".to_string(),
frames: vec![
Frame { id, number: 0, data: b"seven__".to_vec(), ..Default::default() },
Frame { id, number: 1, data: b"four".to_vec(), ..Default::default() },
],
should_error: vec![false, false],
sizes: vec![207, 411],
frame_data: Some(b"seven__".to_vec().into()),
},
];
test_cases.into_iter().for_each(run_frame_validity_test);
}
}