kona_proof_interop/
boot.rs1use 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
19pub const L1_HEAD_KEY: U256 = U256::from_be_slice(&[1]);
21
22pub const L2_AGREED_PRE_STATE_KEY: U256 = U256::from_be_slice(&[2]);
24
25pub const L2_CLAIMED_POST_STATE_KEY: U256 = U256::from_be_slice(&[3]);
27
28pub const L2_CLAIMED_TIMESTAMP_KEY: U256 = U256::from_be_slice(&[4]);
30
31pub const L2_ROLLUP_CONFIG_KEY: U256 = U256::from_be_slice(&[6]);
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct BootInfo {
37 pub l1_head: B256,
39 pub agreed_pre_state_commitment: B256,
41 pub agreed_pre_state: PreState,
43 pub claimed_post_state: B256,
45 pub claimed_l2_timestamp: u64,
47 pub rollup_configs: HashMap<u64, RollupConfig>,
49}
50
51impl BootInfo {
52 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 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 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 pub fn rollup_config(&self, chain_id: u64) -> Option<RollupConfig> {
153 self.rollup_configs.get(&chain_id).cloned()
154 }
155}
156
157#[derive(Debug, Error)]
159pub enum BootstrapError {
160 #[error(transparent)]
162 Oracle(#[from] OracleProviderError),
163 #[error("`INVALID` pre-state claim; Post-state {0} unexpected.")]
165 InvalidPostState(B256),
166 #[error("No-op state transition detected; both pre and post states are `INVALID`.")]
168 InvalidToInvalid,
169}
170
171pub(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}