1use crate::{ChannelCompressor, CompressorError};
4use alloc::{vec, vec::Vec};
5use kona_genesis::RollupConfig;
6use kona_protocol::{Batch, ChannelId, Frame};
7use rand::{RngCore, SeedableRng, rngs::SmallRng};
8
9const FRAME_V0_OVERHEAD: usize = 23;
11
12#[derive(Debug, Clone, PartialEq, thiserror::Error)]
14pub enum ChannelOutError {
15 #[error("The channel is already closed")]
17 ChannelClosed,
18 #[error("The max frame size is too small")]
20 MaxFrameSizeTooSmall,
21 #[error("Missing compressed batch data")]
23 MissingData,
24 #[error("Error from compression")]
26 Compression(#[from] CompressorError),
27 #[error("Error encoding the batch")]
29 BatchEncoding,
30 #[error("The encoded batch exceeds the max RLP bytes per channel")]
32 ExceedsMaxRlpBytesPerChannel,
33}
34
35#[allow(missing_debug_implementations)]
37pub struct ChannelOut<'a, C>
38where
39 C: ChannelCompressor,
40{
41 pub id: ChannelId,
43 pub config: &'a RollupConfig,
47 pub rlp_length: u64,
49 pub closed: bool,
51 pub frame_number: u16,
53 pub compressor: C,
55}
56
57impl<'a, C> ChannelOut<'a, C>
58where
59 C: ChannelCompressor,
60{
61 pub const fn new(id: ChannelId, config: &'a RollupConfig, compressor: C) -> Self {
63 Self { id, config, rlp_length: 0, frame_number: 0, closed: false, compressor }
64 }
65
66 pub fn reset(&mut self) {
68 self.rlp_length = 0;
69 self.frame_number = 0;
70 self.closed = false;
71 self.compressor.reset();
72 let mut small_rng = SmallRng::seed_from_u64(43);
76 SmallRng::fill_bytes(&mut small_rng, &mut self.id);
77 }
78
79 pub fn add_batch(&mut self, batch: Batch) -> Result<(), ChannelOutError> {
82 if self.closed {
83 return Err(ChannelOutError::ChannelClosed);
84 }
85
86 let mut buf = vec![];
88 batch.encode(&mut buf).map_err(|_| ChannelOutError::BatchEncoding)?;
89
90 let max_rlp_bytes_per_channel = self.config.max_rlp_bytes_per_channel(batch.timestamp());
92 if self.rlp_length + buf.len() as u64 > max_rlp_bytes_per_channel {
93 return Err(ChannelOutError::ExceedsMaxRlpBytesPerChannel);
94 }
95
96 self.compressor.write(&buf)?;
97
98 Ok(())
99 }
100
101 pub const fn input_bytes(&self) -> u64 {
103 self.rlp_length
104 }
105
106 pub fn ready_bytes(&self) -> usize {
108 self.compressor.len()
109 }
110
111 pub fn flush(&mut self) -> Result<(), ChannelOutError> {
113 self.compressor.flush()?;
114 Ok(())
115 }
116
117 pub const fn close(&mut self) {
119 self.closed = true;
120 }
121
122 pub fn output_frame(&mut self, max_size: usize) -> Result<Frame, ChannelOutError> {
124 if max_size < FRAME_V0_OVERHEAD {
125 return Err(ChannelOutError::MaxFrameSizeTooSmall);
126 }
127
128 let mut frame =
130 Frame { id: self.id, number: self.frame_number, is_last: self.closed, data: vec![] };
131
132 let mut max_size = max_size - FRAME_V0_OVERHEAD;
133 if max_size > self.ready_bytes() {
134 max_size = self.ready_bytes();
135 }
136
137 let mut data = Vec::with_capacity(max_size);
139 self.compressor.read(&mut data).map_err(ChannelOutError::Compression)?;
140 frame.data.extend_from_slice(data.as_slice());
141
142 self.frame_number += 1;
144 Ok(frame)
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::{CompressorWriter, test_utils::MockCompressor};
152 use kona_protocol::{SingleBatch, SpanBatch};
153
154 #[test]
155 fn test_output_frame_max_size_too_small() {
156 let config = RollupConfig::default();
157 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
158 assert_eq!(channel.output_frame(0), Err(ChannelOutError::MaxFrameSizeTooSmall));
159 }
160
161 #[test]
162 fn test_channel_out_output_frame_no_data() {
163 let config = RollupConfig::default();
164 let mut channel = ChannelOut::new(
165 ChannelId::default(),
166 &config,
167 MockCompressor { read_error: true, compressed: Some(Default::default()) },
168 );
169 let err = channel.output_frame(FRAME_V0_OVERHEAD).unwrap_err();
170 assert_eq!(err, ChannelOutError::Compression(CompressorError::Full));
171 }
172
173 #[test]
174 fn test_channel_out_output() {
175 let config = RollupConfig::default();
176 let mut channel = ChannelOut::new(
177 ChannelId::default(),
178 &config,
179 MockCompressor { compressed: Some(Default::default()), ..Default::default() },
180 );
181 let frame = channel.output_frame(FRAME_V0_OVERHEAD).unwrap();
182 assert_eq!(frame.id, ChannelId::default());
183 assert_eq!(frame.number, 0);
184 assert!(!frame.is_last);
185 }
186
187 #[test]
188 fn test_channel_out_reset() {
189 let config = RollupConfig::default();
190 let mut channel = ChannelOut {
191 id: ChannelId::default(),
192 config: &config,
193 rlp_length: 10,
194 closed: true,
195 frame_number: 11,
196 compressor: MockCompressor::default(),
197 };
198 channel.reset();
199 assert_eq!(channel.rlp_length, 0);
200 assert_eq!(channel.frame_number, 0);
201 assert!(channel.id != ChannelId::default());
205 assert!(!channel.closed);
206 }
207
208 #[test]
209 fn test_channel_out_ready_bytes_empty() {
210 let config = RollupConfig::default();
211 let channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
212 assert_eq!(channel.ready_bytes(), 0);
213 }
214
215 #[test]
216 fn test_channel_out_ready_bytes_some() {
217 let config = RollupConfig::default();
218 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
219 channel.compressor.write(&[1, 2, 3]).unwrap();
220 assert_eq!(channel.ready_bytes(), 3);
221 }
222
223 #[test]
224 fn test_channel_out_close() {
225 let config = RollupConfig::default();
226 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
227 assert!(!channel.closed);
228
229 channel.close();
230 assert!(channel.closed);
231 }
232
233 #[test]
234 fn test_channel_out_add_batch_closed() {
235 let config = RollupConfig::default();
236 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
237 channel.close();
238
239 let batch = Batch::Single(SingleBatch::default());
240 assert_eq!(channel.add_batch(batch), Err(ChannelOutError::ChannelClosed));
241 }
242
243 #[test]
244 fn test_channel_out_empty_span_batch_decode_error() {
245 let config = RollupConfig::default();
246 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
247
248 let batch = Batch::Span(SpanBatch::default());
249 assert_eq!(channel.add_batch(batch), Err(ChannelOutError::BatchEncoding));
250 }
251
252 #[test]
253 fn test_channel_out_max_rlp_bytes_per_channel() {
254 let config = RollupConfig::default();
255 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
256
257 let batch = Batch::Single(SingleBatch::default());
258 channel.rlp_length = config.max_rlp_bytes_per_channel(batch.timestamp());
259
260 assert_eq!(channel.add_batch(batch), Err(ChannelOutError::ExceedsMaxRlpBytesPerChannel));
261 }
262
263 #[test]
264 fn test_channel_out_add_batch() {
265 let config = RollupConfig::default();
266 let mut channel = ChannelOut::new(ChannelId::default(), &config, MockCompressor::default());
267
268 let batch = Batch::Single(SingleBatch::default());
269 assert_eq!(channel.add_batch(batch), Ok(()));
270 }
271}