kona_proof_interop/
pre_state.rs1use alloc::vec::Vec;
4use alloy_primitives::{B256, Bytes, b256, keccak256};
5use alloy_rlp::{Buf, Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
6use kona_interop::{OutputRootWithChain, SUPER_ROOT_VERSION, SuperRoot};
7use serde::{Deserialize, Serialize};
8
9pub(crate) const TRANSITION_STATE_VERSION: u8 = 255;
11
12pub const TRANSITION_STATE_MAX_STEPS: u64 = 2u64.pow(7) - 1;
14
15pub const INVALID_TRANSITION: Bytes = Bytes::from_static(b"invalid");
17
18pub const INVALID_TRANSITION_HASH: B256 =
20 b256!("ffd7db0f9d5cdeb49c4c9eba649d4dc6d852d64671e65488e57f58584992ac68");
21
22#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
27#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
28pub enum PreState {
29 SuperRoot(SuperRoot),
31 TransitionState(TransitionState),
33}
34
35impl PreState {
36 pub fn hash(&self) -> B256 {
38 let mut rlp_buf = Vec::with_capacity(self.length());
39 self.encode(&mut rlp_buf);
40 keccak256(&rlp_buf)
41 }
42
43 pub const fn timestamp(&self) -> u64 {
45 match self {
46 Self::SuperRoot(super_root) => super_root.timestamp,
47 Self::TransitionState(transition_state) => transition_state.pre_state.timestamp,
48 }
49 }
50
51 pub fn active_l2_output_root(&self) -> Option<&OutputRootWithChain> {
55 match self {
56 Self::SuperRoot(super_root) => super_root.output_roots.first(),
57 Self::TransitionState(transition_state) => {
58 transition_state.pre_state.output_roots.get(transition_state.step as usize)
59 }
60 }
61 }
62
63 pub fn active_l2_chain_id(&self) -> Option<u64> {
67 self.active_l2_output_root().map(|output_root| output_root.chain_id)
68 }
69
70 pub fn transition(self, optimistic_block: Option<OptimisticBlock>) -> Option<Self> {
72 match self {
73 Self::SuperRoot(super_root) => Some(Self::TransitionState(TransitionState::new(
74 super_root,
75 alloc::vec![optimistic_block?],
76 1,
77 ))),
78 Self::TransitionState(mut transition_state) => {
79 if transition_state.pending_progress.len() ==
83 transition_state.pre_state.output_roots.len()
84 {
85 if transition_state.step == TRANSITION_STATE_MAX_STEPS {
86 let super_root = SuperRoot::new(
87 transition_state.pre_state.timestamp + 1,
88 transition_state
89 .pending_progress
90 .iter()
91 .zip(transition_state.pre_state.output_roots.iter())
92 .map(|(optimistic_block, pre_state_output)| {
93 OutputRootWithChain::new(
94 pre_state_output.chain_id,
95 optimistic_block.output_root,
96 )
97 })
98 .collect(),
99 );
100 return Some(Self::SuperRoot(super_root));
101 } else {
102 transition_state.step += 1;
103 return Some(Self::TransitionState(transition_state));
104 };
105 }
106
107 transition_state.pending_progress.push(optimistic_block?);
108 transition_state.step += 1;
109 Some(Self::TransitionState(transition_state))
110 }
111 }
112 }
113}
114
115impl Encodable for PreState {
116 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
117 match self {
118 Self::SuperRoot(super_root) => {
119 super_root.encode(out);
120 }
121 Self::TransitionState(transition_state) => {
122 transition_state.encode(out);
123 }
124 }
125 }
126}
127
128impl Decodable for PreState {
129 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
130 if buf.is_empty() {
131 return Err(alloy_rlp::Error::UnexpectedLength);
132 }
133
134 match buf[0] {
135 TRANSITION_STATE_VERSION => {
136 let transition_state = TransitionState::decode(buf)?;
137 Ok(Self::TransitionState(transition_state))
138 }
139 SUPER_ROOT_VERSION => {
140 let super_root =
141 SuperRoot::decode(buf).map_err(|_| alloy_rlp::Error::UnexpectedString)?;
142 Ok(Self::SuperRoot(super_root))
143 }
144 _ => Err(alloy_rlp::Error::Custom("invalid version byte")),
145 }
146 }
147}
148
149#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
152#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
153pub struct TransitionState {
154 pub pre_state: SuperRoot,
156 pub pending_progress: Vec<OptimisticBlock>,
158 pub step: u64,
160}
161
162impl TransitionState {
163 pub const fn new(
165 pre_state: SuperRoot,
166 pending_progress: Vec<OptimisticBlock>,
167 step: u64,
168 ) -> Self {
169 Self { pre_state, pending_progress, step }
170 }
171
172 pub fn hash(&self) -> B256 {
174 let mut rlp_buf = Vec::with_capacity(self.length());
175 self.encode(&mut rlp_buf);
176 keccak256(&rlp_buf)
177 }
178
179 pub fn payload_length(&self) -> usize {
181 Header { list: false, payload_length: self.pre_state.encoded_length() }.length() +
182 self.pre_state.encoded_length() +
183 self.pending_progress.length() +
184 self.step.length()
185 }
186}
187
188impl Encodable for TransitionState {
189 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
190 out.put_u8(TRANSITION_STATE_VERSION);
191
192 Header { list: true, payload_length: self.payload_length() }.encode(out);
193
194 let mut pre_state_buf = Vec::new();
197 self.pre_state.encode(&mut pre_state_buf);
198 Bytes::from(pre_state_buf).encode(out);
199
200 self.pending_progress.encode(out);
201 self.step.encode(out);
202 }
203}
204
205impl Decodable for TransitionState {
206 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
207 if buf.is_empty() {
208 return Err(alloy_rlp::Error::UnexpectedLength);
209 }
210
211 let version = buf[0];
212 if version != TRANSITION_STATE_VERSION {
213 return Err(alloy_rlp::Error::Custom("invalid version byte"));
214 }
215 buf.advance(1);
216
217 let header = Header::decode(buf)?;
219 if !header.list {
220 return Err(alloy_rlp::Error::UnexpectedString);
221 }
222
223 let pre_state_buf = Bytes::decode(buf)?;
226 let pre_state = SuperRoot::decode(&mut pre_state_buf.as_ref())
227 .map_err(|_| alloy_rlp::Error::UnexpectedString)?;
228
229 let pending_progress = Vec::<OptimisticBlock>::decode(buf)?;
231 let step = u64::decode(buf)?;
232
233 Ok(Self { pre_state, pending_progress, step })
234 }
235}
236
237#[derive(
239 Default, Debug, Clone, Eq, PartialEq, RlpEncodable, RlpDecodable, Serialize, Deserialize,
240)]
241#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
242pub struct OptimisticBlock {
243 pub block_hash: B256,
245 pub output_root: B256,
247}
248
249impl OptimisticBlock {
250 pub const fn new(block_hash: B256, output_root: B256) -> Self {
252 Self { block_hash, output_root }
253 }
254}
255
256#[cfg(test)]
257mod test {
258 use super::{OptimisticBlock, SuperRoot, TransitionState};
259 use alloc::{vec, vec::Vec};
260 use alloy_primitives::B256;
261 use alloy_rlp::{Decodable, Encodable};
262 use kona_interop::OutputRootWithChain;
263
264 #[test]
265 fn test_static_transition_state_roundtrip() {
266 let transition_state = TransitionState::new(
267 SuperRoot::new(
268 10,
269 vec![
270 (OutputRootWithChain::new(1, B256::default())),
271 (OutputRootWithChain::new(2, B256::default())),
272 ],
273 ),
274 vec![OptimisticBlock::default(), OptimisticBlock::default()],
275 1,
276 );
277
278 let mut rlp_buf = Vec::with_capacity(transition_state.length());
279 transition_state.encode(&mut rlp_buf);
280
281 assert_eq!(transition_state, TransitionState::decode(&mut rlp_buf.as_slice()).unwrap());
282 }
283
284 #[test]
285 #[cfg(feature = "arbitrary")]
286 fn test_arbitrary_pre_state_roundtrip() {
287 use arbitrary::Arbitrary;
288 use rand::Rng;
289 let mut bytes = [0u8; 1024];
290 rand::rng().fill(bytes.as_mut_slice());
291 let pre_state =
292 super::PreState::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
293
294 let mut rlp_buf = Vec::with_capacity(pre_state.length());
295 pre_state.encode(&mut rlp_buf);
296 assert_eq!(pre_state, super::PreState::decode(&mut rlp_buf.as_slice()).unwrap());
297 }
298
299 #[test]
300 #[cfg(feature = "arbitrary")]
301 fn test_arbitrary_transition_state_roundtrip() {
302 use arbitrary::Arbitrary;
303 use rand::Rng;
304 let mut bytes = [0u8; 1024];
305 rand::rng().fill(bytes.as_mut_slice());
306 let transition_state =
307 TransitionState::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
308
309 let mut rlp_buf = Vec::with_capacity(transition_state.length());
310 transition_state.encode(&mut rlp_buf);
311 assert_eq!(transition_state, TransitionState::decode(&mut rlp_buf.as_slice()).unwrap());
312 }
313}