Skip to content

Commit c92a5ca

Browse files
authored
feat: signet-journal crate (#117)
1 parent 8869b3f commit c92a5ca

File tree

7 files changed

+836
-0
lines changed

7 files changed

+836
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ signet-bundle = { version = "0.10", path = "crates/bundle" }
3838
signet-constants = { version = "0.10", path = "crates/constants" }
3939
signet-evm = { version = "0.10", path = "crates/evm" }
4040
signet-extract = { version = "0.10", path = "crates/extract" }
41+
signet-journal = { version = "0.10", path = "crates/journal" }
4142
signet-node = { version = "0.10", path = "crates/node" }
4243
signet-sim = { version = "0.10", path = "crates/sim" }
4344
signet-types = { version = "0.10", path = "crates/types" }
4445
signet-tx-cache = { version = "0.10", path = "crates/tx-cache" }
4546
signet-zenith = { version = "0.10", path = "crates/zenith" }
47+
4648
signet-test-utils = { version = "0.10", path = "crates/test-utils" }
4749

4850
# ajj

crates/journal/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "signet-journal"
3+
description = "Utilities for working with trevm journals in the Signet chain."
4+
version.workspace = true
5+
edition.workspace = true
6+
rust-version.workspace = true
7+
authors.workspace = true
8+
license.workspace = true
9+
homepage.workspace = true
10+
repository.workspace = true
11+
12+
[dependencies]
13+
alloy.workspace = true
14+
futures-util = "0.3.31"
15+
thiserror.workspace = true
16+
trevm.workspace = true

crates/journal/src/host.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use crate::JournalMeta;
2+
use alloy::{
3+
consensus::Header,
4+
primitives::{keccak256, Bytes, B256},
5+
};
6+
use std::sync::OnceLock;
7+
use trevm::journal::{BundleStateIndex, JournalDecode, JournalDecodeError, JournalEncode};
8+
9+
/// Journal associated with a host block. The journal is an index over the EVM
10+
/// state changes. It can be used to repopulate
11+
#[derive(Debug, Clone)]
12+
pub struct HostJournal<'a> {
13+
/// The metadata
14+
meta: JournalMeta,
15+
16+
/// The changes.
17+
journal: BundleStateIndex<'a>,
18+
19+
/// The serialized journal
20+
serialized: OnceLock<Bytes>,
21+
22+
/// The hash of the serialized journal
23+
hash: OnceLock<B256>,
24+
}
25+
26+
impl PartialEq for HostJournal<'_> {
27+
fn eq(&self, other: &Self) -> bool {
28+
self.meta == other.meta && self.journal == other.journal
29+
}
30+
}
31+
32+
impl Eq for HostJournal<'_> {}
33+
34+
impl<'a> HostJournal<'a> {
35+
/// Create a new journal.
36+
pub const fn new(meta: JournalMeta, journal: BundleStateIndex<'a>) -> Self {
37+
Self { meta, journal, serialized: OnceLock::new(), hash: OnceLock::new() }
38+
}
39+
40+
/// Deconstruct the `HostJournal` into its parts.
41+
pub fn into_parts(self) -> (JournalMeta, BundleStateIndex<'a>) {
42+
(self.meta, self.journal)
43+
}
44+
45+
/// Get the journal meta.
46+
pub const fn meta(&self) -> &JournalMeta {
47+
&self.meta
48+
}
49+
50+
/// Get the journal.
51+
pub const fn journal(&self) -> &BundleStateIndex<'a> {
52+
&self.journal
53+
}
54+
55+
/// Get the host height.
56+
pub const fn host_height(&self) -> u64 {
57+
self.meta.host_height()
58+
}
59+
60+
/// Get the previous journal hash.
61+
pub const fn prev_journal_hash(&self) -> B256 {
62+
self.meta.prev_journal_hash()
63+
}
64+
65+
/// Get the rollup block header.
66+
pub const fn header(&self) -> &Header {
67+
self.meta.header()
68+
}
69+
70+
/// Get the rollup height.
71+
pub const fn rollup_height(&self) -> u64 {
72+
self.meta.rollup_height()
73+
}
74+
75+
/// Serialize the journal.
76+
pub fn serialized(&self) -> &Bytes {
77+
self.serialized.get_or_init(|| JournalEncode::encoded(self))
78+
}
79+
80+
/// Serialize and hash the journal.
81+
pub fn journal_hash(&self) -> B256 {
82+
*self.hash.get_or_init(|| keccak256(self.serialized()))
83+
}
84+
}
85+
86+
impl JournalEncode for HostJournal<'_> {
87+
fn serialized_size(&self) -> usize {
88+
8 + 32 + self.journal.serialized_size()
89+
}
90+
91+
fn encode(&self, buf: &mut dyn alloy::rlp::BufMut) {
92+
self.meta.encode(buf);
93+
self.journal.encode(buf);
94+
}
95+
}
96+
97+
impl JournalDecode for HostJournal<'static> {
98+
fn decode(buf: &mut &[u8]) -> Result<Self, JournalDecodeError> {
99+
let original = *buf;
100+
101+
let meta = JournalMeta::decode(buf)?;
102+
let journal = JournalDecode::decode(buf)?;
103+
104+
let bytes_read = original.len() - buf.len();
105+
let original = &original[..bytes_read];
106+
107+
Ok(Self {
108+
meta,
109+
journal,
110+
serialized: OnceLock::from(Bytes::copy_from_slice(original)),
111+
hash: OnceLock::from(keccak256(original)),
112+
})
113+
}
114+
}
115+
116+
#[cfg(test)]
117+
pub(crate) mod test {
118+
use super::*;
119+
use alloy::primitives::{Address, KECCAK256_EMPTY, U256};
120+
use std::{borrow::Cow, collections::BTreeMap};
121+
use trevm::{
122+
journal::{AcctDiff, InfoOutcome},
123+
revm::{
124+
database::states::StorageSlot,
125+
state::{AccountInfo, Bytecode},
126+
},
127+
};
128+
129+
pub(crate) fn make_state_diff() -> BundleStateIndex<'static> {
130+
let mut bsi = BundleStateIndex::default();
131+
132+
let bytecode = Bytecode::new_legacy(Bytes::from_static(b"world"));
133+
let code_hash = bytecode.hash_slow();
134+
135+
bsi.new_contracts.insert(code_hash, Cow::Owned(bytecode));
136+
137+
bsi.state.insert(
138+
Address::repeat_byte(0x99),
139+
AcctDiff {
140+
outcome: InfoOutcome::Diff {
141+
old: Cow::Owned(AccountInfo {
142+
balance: U256::from(38),
143+
nonce: 7,
144+
code_hash: KECCAK256_EMPTY,
145+
code: None,
146+
}),
147+
new: Cow::Owned(AccountInfo {
148+
balance: U256::from(23828839),
149+
nonce: 83,
150+
code_hash,
151+
code: None,
152+
}),
153+
},
154+
storage_diff: BTreeMap::from_iter([(
155+
U256::MAX,
156+
Cow::Owned(StorageSlot {
157+
previous_or_original_value: U256::from(123456),
158+
present_value: U256::from(654321),
159+
}),
160+
)]),
161+
},
162+
);
163+
bsi
164+
}
165+
166+
#[test]
167+
fn roundtrip() {
168+
let original = HostJournal::new(
169+
JournalMeta::new(u64::MAX, B256::repeat_byte(0xff), Header::default()),
170+
make_state_diff(),
171+
);
172+
173+
let buf = original.encoded();
174+
175+
let decoded = HostJournal::decode(&mut &buf[..]).unwrap();
176+
assert_eq!(original, decoded);
177+
}
178+
}

crates/journal/src/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! Signet journal utilities.
2+
//!
3+
//! In general, it is recommended to use the [`Journal`] enum, for forwards
4+
//! compatibility.
5+
6+
#![warn(
7+
missing_copy_implementations,
8+
missing_debug_implementations,
9+
missing_docs,
10+
unreachable_pub,
11+
clippy::missing_const_for_fn,
12+
rustdoc::all
13+
)]
14+
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
15+
#![deny(unused_must_use, rust_2018_idioms)]
16+
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
17+
18+
mod host;
19+
pub use host::HostJournal;
20+
21+
mod meta;
22+
pub use meta::JournalMeta;
23+
24+
mod set;
25+
pub use set::JournalSet;
26+
27+
mod versions;
28+
pub use versions::Journal;
29+
30+
use futures_util::Stream;
31+
32+
/// Any [`Stream`] that produces [`Journal`]s.
33+
pub trait JournalStream<'a>: Stream<Item = Journal<'a>> {}
34+
35+
impl<'a, S> JournalStream<'a> for S where S: Stream<Item = Journal<'a>> {}

crates/journal/src/meta.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use alloy::{consensus::Header, primitives::B256};
2+
use trevm::journal::{JournalDecode, JournalDecodeError, JournalEncode};
3+
4+
/// Metadata for a block journal. This includes the block header, the host
5+
/// height, and the hash of the previous journal.
6+
#[derive(Debug, Clone, PartialEq, Eq)]
7+
pub struct JournalMeta {
8+
/// The host height.
9+
host_height: u64,
10+
11+
/// The hash of the previous journal.
12+
prev_journal_hash: B256,
13+
14+
/// The rollup block header.
15+
header: Header,
16+
}
17+
18+
impl JournalMeta {
19+
/// Create a new `JournalMeta`.
20+
pub const fn new(host_height: u64, prev_journal_hash: B256, header: Header) -> Self {
21+
Self { host_height, prev_journal_hash, header }
22+
}
23+
24+
/// Deconstruct the `JournalMeta` into its parts.
25+
pub fn into_parts(self) -> (u64, B256, Header) {
26+
(self.host_height, self.prev_journal_hash, self.header)
27+
}
28+
29+
/// Get the host height.
30+
pub const fn host_height(&self) -> u64 {
31+
self.host_height
32+
}
33+
34+
/// Get the previous journal hash.
35+
pub const fn prev_journal_hash(&self) -> B256 {
36+
self.prev_journal_hash
37+
}
38+
39+
/// Get the rollup block header.
40+
pub const fn header(&self) -> &Header {
41+
&self.header
42+
}
43+
44+
/// Get the rollup height.
45+
pub const fn rollup_height(&self) -> u64 {
46+
self.header.number
47+
}
48+
}
49+
50+
impl JournalEncode for JournalMeta {
51+
fn serialized_size(&self) -> usize {
52+
8 + 32 + self.header.serialized_size()
53+
}
54+
55+
fn encode(&self, buf: &mut dyn alloy::rlp::BufMut) {
56+
self.host_height.encode(buf);
57+
self.prev_journal_hash.encode(buf);
58+
self.header.encode(buf);
59+
}
60+
}
61+
62+
impl JournalDecode for JournalMeta {
63+
fn decode(buf: &mut &[u8]) -> Result<Self, JournalDecodeError> {
64+
let host_height = JournalDecode::decode(buf)?;
65+
let prev_journal_hash = JournalDecode::decode(buf)?;
66+
let header = JournalDecode::decode(buf)?;
67+
68+
Ok(Self { host_height, prev_journal_hash, header })
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod test {
74+
use super::*;
75+
76+
#[test]
77+
fn roundtrip() {
78+
let original = JournalMeta {
79+
host_height: 13871,
80+
prev_journal_hash: B256::repeat_byte(0x7),
81+
header: Header::default(),
82+
};
83+
84+
let buf = original.encoded();
85+
86+
let decoded = JournalMeta::decode(&mut &buf[..]).unwrap();
87+
assert_eq!(original, decoded);
88+
}
89+
}

0 commit comments

Comments
 (0)