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