1use crate::Journal;
2use alloy::primitives::B256;
3use std::{collections::VecDeque, ops::RangeInclusive};
4
5#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
6pub enum JournalSetError<'a> {
7 #[error("wrong height: actual {actual}, expected {expected}")]
9 WrongHeight {
10 actual: u64,
12
13 expected: u64,
15
16 journal: Box<Journal<'a>>,
18 },
19
20 #[error("wrong prev_hash: current {latest_hash}, new journal expected {in_journal}")]
22 WrongPrevHash {
23 latest_hash: B256,
25
26 in_journal: B256,
28
29 journal: Box<Journal<'a>>,
31 },
32
33 #[error("not in range: start {start:?}, end {end:?}, height {height}")]
35 NotInRange {
36 start: Option<u64>,
38
39 end: Option<u64>,
41
42 height: u64,
44
45 journal: Box<Journal<'a>>,
47 },
48}
49
50impl<'a> JournalSetError<'a> {
51 pub fn into_journal(self) -> Journal<'a> {
53 match self {
54 Self::WrongHeight { journal, .. } => *journal,
55 Self::WrongPrevHash { journal, .. } => *journal,
56 Self::NotInRange { journal, .. } => *journal,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct JournalSet<'a> {
64 journals: VecDeque<Journal<'a>>,
66
67 latest_height: Option<u64>,
70
71 latest_hash: Option<B256>,
73}
74
75impl<'a> JournalSet<'a> {
76 pub const fn new() -> Self {
78 Self { journals: VecDeque::new(), latest_height: None, latest_hash: None }
79 }
80
81 pub fn with_capacity(capacity: usize) -> Self {
83 Self { journals: VecDeque::with_capacity(capacity), latest_height: None, latest_hash: None }
84 }
85
86 pub fn from_journal(journal: Journal<'a>) -> Self {
88 let latest_height = Some(journal.rollup_height());
89 let latest_hash = Some(journal.journal_hash());
90 let mut journals = VecDeque::new();
91 journals.push_back(journal);
92 Self { journals, latest_height, latest_hash }
93 }
94
95 pub fn len(&self) -> usize {
97 self.journals.len()
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.journals.is_empty()
103 }
104
105 fn not_in_range(&self, journal: Journal<'a>) -> JournalSetError<'a> {
107 JournalSetError::NotInRange {
108 start: self.earliest_height(),
109 end: self.latest_height(),
110 height: journal.rollup_height(),
111 journal: Box::new(journal),
112 }
113 }
114
115 fn wrong_prev_hash(&self, journal: Journal<'a>) -> JournalSetError<'a> {
117 JournalSetError::WrongPrevHash {
118 latest_hash: self.latest_hash().expect("condition of use"),
119 in_journal: journal.prev_journal_hash(),
120 journal: Box::new(journal),
121 }
122 }
123
124 fn wrong_height(&self, journal: Journal<'a>) -> JournalSetError<'a> {
126 JournalSetError::WrongHeight {
127 actual: journal.rollup_height(),
128 expected: self.latest_height().expect("condition of use") + 1,
129 journal: Box::new(journal),
130 }
131 }
132
133 pub fn earliest_height(&self) -> Option<u64> {
135 if let Some(journal) = self.journals.front() {
136 return Some(journal.rollup_height());
137 }
138 None
139 }
140
141 pub const fn latest_hash(&self) -> Option<B256> {
143 self.latest_hash
144 }
145
146 pub const fn latest_height(&self) -> Option<u64> {
148 self.latest_height
149 }
150
151 fn index_of(&self, rollup_height: u64) -> Option<usize> {
154 let start = self.earliest_height()?;
155 if rollup_height < start || rollup_height > self.latest_height()? {
156 return None;
157 }
158
159 Some((rollup_height - start) as usize)
160 }
161
162 pub fn get_by_rollup_height(&self, rollup_height: u64) -> Option<&Journal<'a>> {
164 let index = self.index_of(rollup_height)?;
165 self.journals.get(index)
166 }
167
168 pub fn range(&self) -> Option<RangeInclusive<u64>> {
171 let start = self.earliest_height()?;
172 let end = self.latest_height()?;
173
174 Some(start..=end)
175 }
176
177 fn check_last_height(&self, journal: Journal<'a>) -> Result<Journal<'a>, JournalSetError<'a>> {
179 if let Some(latest_height) = self.latest_height() {
182 if journal.rollup_height() != latest_height + 1 {
183 return Err(self.wrong_height(journal));
184 }
185 }
186 Ok(journal)
187 }
188
189 fn check_prev_hash(&self, journal: Journal<'a>) -> Result<Journal<'a>, JournalSetError<'a>> {
191 if let Some(latest_hash) = self.latest_hash() {
194 if journal.prev_journal_hash() != latest_hash {
195 return Err(self.wrong_prev_hash(journal));
196 }
197 }
198 Ok(journal)
199 }
200
201 fn unwind_to(&mut self, journal: &Journal<'a>) {
207 let Some(idx) = self.index_of(journal.rollup_height()) else {
208 unreachable!("condition of use");
209 };
210
211 self.journals.truncate(idx + 1);
217 self.pop_back();
218 }
219
220 fn append_inner(&mut self, journal: Journal<'a>) {
221 self.latest_height = Some(journal.rollup_height());
222 self.latest_hash = Some(journal.journal_hash());
223 self.journals.push_back(journal);
224 }
225
226 pub fn try_append(&mut self, journal: Journal<'a>) -> Result<(), JournalSetError<'a>> {
228 let journal = self.check_last_height(journal)?;
230 let journal = self.check_prev_hash(journal)?;
231
232 self.append_inner(journal);
233
234 Ok(())
235 }
236
237 pub fn append_overwrite(&mut self, journal: Journal<'a>) -> Result<(), JournalSetError<'a>> {
244 let Some(j) = self.get_by_rollup_height(journal.rollup_height()) else {
245 return Err(self.not_in_range(journal));
246 };
247
248 if j.journal_hash() == journal.journal_hash() {
250 return Ok(());
251 }
252
253 if j.rollup_height() != journal.rollup_height() {
254 return Err(self.wrong_height(journal));
255 }
256
257 if j.prev_journal_hash() != journal.prev_journal_hash() {
259 return Err(self.wrong_prev_hash(journal));
260 }
261
262 self.unwind_to(&journal);
263 self.append_inner(journal);
264 Ok(())
265 }
266
267 pub fn pop_front(&mut self) -> Option<Journal<'a>> {
269 self.journals.pop_front()
270 }
271
272 pub fn pop_back(&mut self) -> Option<Journal<'a>> {
274 let journal = self.journals.pop_back();
275
276 if let Some(journal) = &journal {
279 self.latest_height = Some(journal.rollup_height() - 1);
280 self.latest_hash = Some(journal.prev_journal_hash());
281 }
282 journal
283 }
284}
285
286impl<'a> IntoIterator for JournalSet<'a> {
287 type Item = Journal<'a>;
288 type IntoIter = std::collections::vec_deque::IntoIter<Journal<'a>>;
289
290 fn into_iter(self) -> Self::IntoIter {
291 self.journals.into_iter()
292 }
293}
294
295#[cfg(test)]
296mod test {
297 use super::*;
298 use crate::{HostJournal, JournalMeta};
299 use alloy::{consensus::Header, primitives::Bytes};
300 use std::borrow::Cow;
301 use trevm::{journal::BundleStateIndex, revm::state::Bytecode};
302
303 fn journal_at_heights(host: u64, rollup: u64, prev_hash: B256) -> Journal<'static> {
304 let meta = JournalMeta::new(
305 host,
306 prev_hash,
307 Cow::Owned(Header { number: rollup, ..Default::default() }),
308 );
309 let host = HostJournal::new(meta, Default::default());
310
311 Journal::V1(host)
312 }
313
314 #[test]
315 fn basic_consistency() {
316 let mut set = JournalSet::new();
317
318 let j0 = journal_at_heights(100, 0, B256::repeat_byte(0));
319 let j1 = journal_at_heights(101, 1, j0.journal_hash());
320 let j2 = journal_at_heights(102, 2, j1.journal_hash());
321 let j3 = journal_at_heights(103, 3, j2.journal_hash());
322
323 assert_eq!(set.earliest_height(), None);
325 assert_eq!(set.latest_height(), None);
326 assert_eq!(set.latest_hash(), None);
327 assert_eq!(set.range(), None);
328
329 assert_eq!(set.try_append(j0.clone()), Ok(()));
331 assert_eq!(set.earliest_height(), Some(0));
332 assert_eq!(set.latest_height(), Some(0));
333 assert_eq!(set.latest_hash(), Some(j0.journal_hash()));
334 assert_eq!(set.range(), Some(0..=0));
335
336 assert!(set.try_append(j2.clone()).is_err());
338
339 assert_eq!(set.try_append(j1.clone()), Ok(()));
341 assert_eq!(set.earliest_height(), Some(0));
342 assert_eq!(set.latest_height(), Some(1));
343 assert_eq!(set.latest_hash(), Some(j1.journal_hash()));
344 assert_eq!(set.range(), Some(0..=1));
345
346 assert!(set.try_append(j3.clone()).is_err());
348
349 let popped = set.pop_front().expect("should pop");
351 assert_eq!(popped, j0);
352 assert_eq!(set.earliest_height(), Some(1));
353 assert_eq!(set.latest_height(), Some(1));
354 assert_eq!(set.latest_hash(), Some(j1.journal_hash()));
355
356 assert_eq!(set.try_append(j2.clone()), Ok(()));
358 assert_eq!(set.earliest_height(), Some(1));
359 assert_eq!(set.latest_height(), Some(2));
360 assert_eq!(set.latest_hash(), Some(j2.journal_hash()));
361 assert_eq!(set.range(), Some(1..=2));
362
363 assert_eq!(set.try_append(j3.clone()), Ok(()));
365 assert_eq!(set.earliest_height(), Some(1));
366 assert_eq!(set.latest_height(), Some(3));
367 assert_eq!(set.latest_hash(), Some(j3.journal_hash()));
368 assert_eq!(set.range(), Some(1..=3));
369
370 let popped = set.pop_front().expect("should pop");
372 assert_eq!(popped, j1);
373 assert_eq!(set.earliest_height(), Some(2));
374 assert_eq!(set.latest_height(), Some(3));
375 assert_eq!(set.latest_hash(), Some(j3.journal_hash()));
376
377 assert!(set.try_append(j0.clone()).is_err());
379 }
380
381 #[test]
382 fn append_overwrite() {
383 let mut set = JournalSet::new();
384
385 let j0 = journal_at_heights(100, 0, B256::repeat_byte(0));
386 let j1 = journal_at_heights(101, 1, j0.journal_hash());
387 let j2 = journal_at_heights(102, 2, j1.journal_hash());
388 let j3 = journal_at_heights(103, 3, j2.journal_hash());
389
390 let mut j1_alt_state = BundleStateIndex::default();
391 j1_alt_state.new_contracts.insert(
392 B256::repeat_byte(1),
393 std::borrow::Cow::Owned(Bytecode::new_legacy(Bytes::from_static(&[0, 1, 2, 3]))),
394 );
395 let j1_alt = Journal::V1(HostJournal::new(
396 JournalMeta::new(
397 101,
398 j0.journal_hash(),
399 Cow::Owned(Header { number: 1, ..Default::default() }),
400 ),
401 j1_alt_state,
402 ));
403
404 assert!(set.try_append(j0.clone()).is_ok());
406 assert!(set.try_append(j1).is_ok());
407 assert!(set.try_append(j2.clone()).is_ok());
408 assert!(set.try_append(j3).is_ok());
409 assert_eq!(set.len(), 4);
410
411 assert!(set.append_overwrite(j1_alt).is_ok());
413 assert_eq!(set.len(), 2);
414
415 assert!(set.try_append(j2).is_err());
417 }
418}