kona_proof_interop/
boot.rs

1//! This module contains the prologue phase of the client program, pulling in the boot information
2//! through the `PreimageOracle` ABI as local keys.
3
4use crate::{HintType, INVALID_TRANSITION, INVALID_TRANSITION_HASH, PreState};
5use alloc::{string::ToString, vec::Vec};
6use alloy_primitives::{B256, Bytes, U256};
7use alloy_rlp::Decodable;
8use kona_genesis::RollupConfig;
9use kona_preimage::{
10    CommsClient, HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient,
11    errors::PreimageOracleError,
12};
13use kona_proof::errors::OracleProviderError;
14use kona_registry::{HashMap, ROLLUP_CONFIGS};
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17use tracing::warn;
18
19/// The local key ident for the L1 head hash.
20pub const L1_HEAD_KEY: U256 = U256::from_be_slice(&[1]);
21
22/// The local key ident for the agreed upon L2 pre-state claim.
23pub const L2_AGREED_PRE_STATE_KEY: U256 = U256::from_be_slice(&[2]);
24
25/// The local key ident for the L2 post-state claim.
26pub const L2_CLAIMED_POST_STATE_KEY: U256 = U256::from_be_slice(&[3]);
27
28/// The local key ident for the L2 claim timestamp.
29pub const L2_CLAIMED_TIMESTAMP_KEY: U256 = U256::from_be_slice(&[4]);
30
31/// The local key ident for the L2 rollup config.
32pub const L2_ROLLUP_CONFIG_KEY: U256 = U256::from_be_slice(&[6]);
33
34/// The boot information for the interop client program.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct BootInfo {
37    /// The L1 head hash containing the safe L2 chain data that may reproduce the post-state claim.
38    pub l1_head: B256,
39    /// The agreed upon superchain pre-state commitment.
40    pub agreed_pre_state_commitment: B256,
41    /// The agreed upon superchain pre-state.
42    pub agreed_pre_state: PreState,
43    /// The claimed (disputed) superchain post-state commitment.
44    pub claimed_post_state: B256,
45    /// The L2 claim timestamp.
46    pub claimed_l2_timestamp: u64,
47    /// The rollup config for the L2 chain.
48    pub rollup_configs: HashMap<u64, RollupConfig>,
49}
50
51impl BootInfo {
52    /// Load the boot information from the preimage oracle.
53    ///
54    /// ## Takes
55    /// - `oracle`: The preimage oracle reader.
56    ///
57    /// ## Returns
58    /// - `Ok(BootInfo)`: The boot information.
59    /// - `Err(_)`: Failed to load the boot information.
60    pub async fn load<O>(oracle: &O) -> Result<Self, BootstrapError>
61    where
62        O: PreimageOracleClient + HintWriterClient + Clone + Send,
63    {
64        let mut l1_head: B256 = B256::ZERO;
65        oracle
66            .get_exact(PreimageKey::new_local(L1_HEAD_KEY.to()), l1_head.as_mut())
67            .await
68            .map_err(OracleProviderError::Preimage)?;
69
70        let mut l2_pre: B256 = B256::ZERO;
71        oracle
72            .get_exact(PreimageKey::new_local(L2_AGREED_PRE_STATE_KEY.to()), l2_pre.as_mut())
73            .await
74            .map_err(OracleProviderError::Preimage)?;
75
76        let mut l2_post: B256 = B256::ZERO;
77        oracle
78            .get_exact(PreimageKey::new_local(L2_CLAIMED_POST_STATE_KEY.to()), l2_post.as_mut())
79            .await
80            .map_err(OracleProviderError::Preimage)?;
81
82        let l2_claim_block = u64::from_be_bytes(
83            oracle
84                .get(PreimageKey::new_local(L2_CLAIMED_TIMESTAMP_KEY.to()))
85                .await
86                .map_err(OracleProviderError::Preimage)?
87                .as_slice()
88                .try_into()
89                .map_err(OracleProviderError::SliceConversion)?,
90        );
91
92        let raw_pre_state = read_raw_pre_state(oracle, l2_pre).await?;
93        if raw_pre_state == INVALID_TRANSITION {
94            warn!(
95                target: "boot-loader",
96                "Invalid pre-state, short-circuiting to check post-state claim."
97            );
98
99            if l2_post == INVALID_TRANSITION_HASH {
100                return Err(BootstrapError::InvalidToInvalid);
101            } else {
102                return Err(BootstrapError::InvalidPostState(l2_post));
103            }
104        }
105
106        let agreed_pre_state =
107            PreState::decode(&mut raw_pre_state.as_ref()).map_err(OracleProviderError::Rlp)?;
108
109        let chain_ids: Vec<_> = match agreed_pre_state {
110            PreState::SuperRoot(ref super_root) => {
111                super_root.output_roots.iter().map(|r| r.chain_id).collect()
112            }
113            PreState::TransitionState(ref transition_state) => {
114                transition_state.pre_state.output_roots.iter().map(|r| r.chain_id).collect()
115            }
116        };
117
118        // Attempt to load the rollup config from the chain ID. If there is no config for the chain,
119        // fall back to loading the config from the preimage oracle.
120        let rollup_configs = if chain_ids.iter().all(|id| ROLLUP_CONFIGS.contains_key(id)) {
121            chain_ids.iter().map(|id| (*id, ROLLUP_CONFIGS[id].clone())).collect()
122        } else {
123            warn!(
124                target: "boot-loader",
125                "No rollup config found for chain IDs {:?}, falling back to preimage oracle. This is insecure in production without additional validation!",
126                chain_ids
127            );
128            let ser_cfg = oracle
129                .get(PreimageKey::new_local(L2_ROLLUP_CONFIG_KEY.to()))
130                .await
131                .map_err(OracleProviderError::Preimage)?;
132            serde_json::from_slice(&ser_cfg).map_err(OracleProviderError::Serde)?
133        };
134
135        Ok(Self {
136            l1_head,
137            rollup_configs,
138            agreed_pre_state_commitment: l2_pre,
139            agreed_pre_state,
140            claimed_post_state: l2_post,
141            claimed_l2_timestamp: l2_claim_block,
142        })
143    }
144
145    /// Returns the [RollupConfig] corresponding to the [PreState::active_l2_chain_id].
146    pub fn active_rollup_config(&self) -> Option<RollupConfig> {
147        let active_l2_chain_id = self.agreed_pre_state.active_l2_chain_id()?;
148        self.rollup_configs.get(&active_l2_chain_id).cloned()
149    }
150
151    /// Returns the [RollupConfig] corresponding to the given `chain_id`.
152    pub fn rollup_config(&self, chain_id: u64) -> Option<RollupConfig> {
153        self.rollup_configs.get(&chain_id).cloned()
154    }
155}
156
157/// An error that occurred during the bootstrapping phase.
158#[derive(Debug, Error)]
159pub enum BootstrapError {
160    /// An error occurred while reading from the preimage oracle.
161    #[error(transparent)]
162    Oracle(#[from] OracleProviderError),
163    /// The pre-state is invalid and the post-state claim is not invalid.
164    #[error("`INVALID` pre-state claim; Post-state {0} unexpected.")]
165    InvalidPostState(B256),
166    /// The pre-state is invalid and the post-state claim is also invalid.
167    #[error("No-op state transition detected; both pre and post states are `INVALID`.")]
168    InvalidToInvalid,
169}
170
171/// Reads the raw pre-state from the preimage oracle.
172pub(crate) async fn read_raw_pre_state<O>(
173    caching_oracle: &O,
174    agreed_pre_state_commitment: B256,
175) -> Result<Bytes, OracleProviderError>
176where
177    O: CommsClient,
178{
179    HintType::AgreedPreState
180        .with_data(&[agreed_pre_state_commitment.as_ref()])
181        .send(caching_oracle)
182        .await?;
183    let pre = caching_oracle
184        .get(PreimageKey::new(*agreed_pre_state_commitment, PreimageKeyType::Keccak256))
185        .await
186        .map_err(OracleProviderError::Preimage)?;
187
188    if pre.is_empty() {
189        return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
190            "Invalid pre-state preimage".to_string(),
191        )));
192    }
193
194    Ok(Bytes::from(pre))
195}