kona_interop/
root.rs

1//! The [SuperRoot] type.
2//!
3//! Represents a snapshot of the state of the superchain at a given integer timestamp.
4
5use crate::{SUPER_ROOT_VERSION, SuperRootError, SuperRootResult};
6use alloc::vec::Vec;
7use alloy_primitives::{B256, Bytes, U256, keccak256};
8use alloy_rlp::{Buf, BufMut};
9
10/// The [SuperRoot] is the snapshot of the superchain at a given timestamp.
11#[derive(Debug, Clone, Eq, PartialEq)]
12#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct SuperRoot {
15    /// The timestamp of the superchain snapshot, in seconds.
16    pub timestamp: u64,
17    /// The chain IDs and output root commitments of all chains within the dependency set.
18    pub output_roots: Vec<OutputRootWithChain>,
19}
20
21impl SuperRoot {
22    /// Create a new [SuperRoot] with the given timestamp and output roots.
23    pub fn new(timestamp: u64, mut output_roots: Vec<OutputRootWithChain>) -> Self {
24        // Guarantee that the output roots are sorted by chain ID.
25        output_roots.sort_by_key(|r| r.chain_id);
26        Self { timestamp, output_roots }
27    }
28
29    /// Decodes a [SuperRoot] from the given buffer.
30    pub fn decode(buf: &mut &[u8]) -> SuperRootResult<Self> {
31        if buf.is_empty() {
32            return Err(SuperRootError::UnexpectedLength);
33        }
34
35        let version = buf[0];
36        if version != SUPER_ROOT_VERSION {
37            return Err(SuperRootError::InvalidVersionByte);
38        }
39        buf.advance(1);
40
41        if buf.len() < 8 {
42            return Err(SuperRootError::UnexpectedLength);
43        }
44        let timestamp = u64::from_be_bytes(buf[0..8].try_into()?);
45        buf.advance(8);
46
47        let mut output_roots = Vec::new();
48        while !buf.is_empty() {
49            if buf.len() < 64 {
50                return Err(SuperRootError::UnexpectedLength);
51            }
52
53            let chain_id = U256::from_be_bytes::<32>(buf[0..32].try_into()?);
54            buf.advance(32);
55            let output_root = B256::from_slice(&buf[0..32]);
56            buf.advance(32);
57            output_roots.push(OutputRootWithChain::new(chain_id.to(), output_root));
58        }
59
60        Ok(Self { timestamp, output_roots })
61    }
62
63    /// Encode the [SuperRoot] into the given buffer.
64    pub fn encode(&self, out: &mut dyn BufMut) {
65        out.put_u8(SUPER_ROOT_VERSION);
66
67        out.put_u64(self.timestamp);
68        for output_root in &self.output_roots {
69            out.put_slice(U256::from(output_root.chain_id).to_be_bytes::<32>().as_slice());
70            out.put_slice(output_root.output_root.as_slice());
71        }
72    }
73
74    /// Returns the encoded length of the [SuperRoot].
75    pub fn encoded_length(&self) -> usize {
76        1 + 8 + 64 * self.output_roots.len()
77    }
78
79    /// Hashes the encoded [SuperRoot] using [keccak256].
80    pub fn hash(&self) -> B256 {
81        let mut rlp_buf = Vec::with_capacity(self.encoded_length());
82        self.encode(&mut rlp_buf);
83        keccak256(&rlp_buf)
84    }
85}
86
87/// Chain Root Info
88#[derive(Debug, Clone, Eq, PartialEq)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
91pub struct ChainRootInfo {
92    /// The chain ID.
93    #[cfg_attr(feature = "serde", serde(rename = "chainID"))]
94    pub chain_id: u64,
95    /// The canonical output root of the latest canonical block at a particular timestamp.
96    pub canonical: B256,
97    /// The pending output root.
98    ///
99    /// This is the output root preimage for the latest block at a particular timestamp prior to
100    /// validation of executing messages. If the original block was valid, this will be the
101    /// preimage of the output root from the `canonical` array. If it was invalid, it will be
102    /// the output root preimage from the optimistic block deposited transaction added to the
103    /// deposit-only block.
104    pub pending: Bytes,
105}
106
107/// The super root response type.
108#[derive(Debug, Clone, Eq, PartialEq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
111pub struct SuperRootResponse {
112    /// The timestamp of the super root.
113    pub timestamp: u64,
114    /// The super root hash.
115    pub super_root: B256,
116    /// The chain root info for each chain in the dependency set.
117    /// It represents the state of the chain at or before the timestamp.
118    pub chains: Vec<ChainRootInfo>,
119}
120
121/// A wrapper around an output root hash with the chain ID it belongs to.
122#[derive(Debug, Clone, Eq, PartialEq, Hash)]
123#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125pub struct OutputRootWithChain {
126    /// The chain ID of the output root.
127    pub chain_id: u64,
128    /// The output root hash.
129    pub output_root: B256,
130}
131
132impl OutputRootWithChain {
133    /// Create a new [OutputRootWithChain] with the given chain ID and output root hash.
134    pub const fn new(chain_id: u64, output_root: B256) -> Self {
135        Self { chain_id, output_root }
136    }
137}
138
139#[cfg(test)]
140mod test {
141    use crate::{SUPER_ROOT_VERSION, errors::SuperRootError};
142
143    use super::{OutputRootWithChain, SuperRoot};
144    use alloy_primitives::{B256, b256};
145
146    #[test]
147    fn test_super_root_sorts_outputs() {
148        let super_root = SuperRoot::new(
149            10,
150            vec![
151                (OutputRootWithChain::new(3, B256::default())),
152                (OutputRootWithChain::new(2, B256::default())),
153                (OutputRootWithChain::new(1, B256::default())),
154            ],
155        );
156
157        assert!(super_root.output_roots.windows(2).all(|w| w[0].chain_id <= w[1].chain_id));
158    }
159
160    #[test]
161    fn test_super_root_empty_buf() {
162        let buf: Vec<u8> = Vec::new();
163        assert!(matches!(
164            SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
165            SuperRootError::UnexpectedLength
166        ));
167    }
168
169    #[test]
170    fn test_super_root_invalid_version() {
171        let buf = vec![0xFF];
172        assert!(matches!(
173            SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
174            SuperRootError::InvalidVersionByte
175        ));
176    }
177
178    #[test]
179    fn test_super_root_invalid_length_at_timestamp() {
180        let buf = vec![SUPER_ROOT_VERSION, 0x00];
181        assert!(matches!(
182            SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
183            SuperRootError::UnexpectedLength
184        ));
185    }
186
187    #[test]
188    fn test_super_root_invalid_length_malformed_output_roots() {
189        let buf = [&[SUPER_ROOT_VERSION], 64u64.to_be_bytes().as_ref(), &[0xbe, 0xef]].concat();
190        assert!(matches!(
191            SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
192            SuperRootError::UnexpectedLength
193        ));
194    }
195
196    #[test]
197    fn test_static_hash_super_root() {
198        const EXPECTED: B256 =
199            b256!("0980033cbf4337f614a2401ab7efbfdc66ab647812f1c98d891d92ddfb376541");
200
201        let super_root = SuperRoot::new(
202            10,
203            vec![
204                (OutputRootWithChain::new(1, B256::default())),
205                (OutputRootWithChain::new(2, B256::default())),
206            ],
207        );
208        assert_eq!(super_root.hash(), EXPECTED);
209    }
210
211    #[test]
212    fn test_static_super_root_roundtrip() {
213        let super_root = SuperRoot::new(
214            10,
215            vec![
216                (OutputRootWithChain::new(1, B256::default())),
217                (OutputRootWithChain::new(2, B256::default())),
218            ],
219        );
220
221        let mut rlp_buf = Vec::with_capacity(super_root.encoded_length());
222        super_root.encode(&mut rlp_buf);
223        assert_eq!(super_root, SuperRoot::decode(&mut rlp_buf.as_slice()).unwrap());
224    }
225
226    #[test]
227    fn test_arbitrary_super_root_roundtrip() {
228        use arbitrary::Arbitrary;
229        use rand::Rng;
230
231        let mut bytes = [0u8; 1024];
232        rand::rng().fill(bytes.as_mut_slice());
233        let super_root = SuperRoot::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
234
235        let mut rlp_buf = Vec::with_capacity(super_root.encoded_length());
236        super_root.encode(&mut rlp_buf);
237        assert_eq!(super_root, SuperRoot::decode(&mut rlp_buf.as_slice()).unwrap());
238    }
239}