diff --git a/Cargo.lock b/Cargo.lock
index 1293ea092..680d87827 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2662,6 +2662,7 @@ dependencies = [
"async-trait",
"op-alloy-consensus",
"rand 0.9.0",
+ "serde",
"thiserror 2.0.11",
"tokio",
"tracing",
diff --git a/bin/client/src/interop/consolidate.rs b/bin/client/src/interop/consolidate.rs
index 52efd8f50..7dc0ae47b 100644
--- a/bin/client/src/interop/consolidate.rs
+++ b/bin/client/src/interop/consolidate.rs
@@ -20,18 +20,17 @@ use tracing::info;
pub(crate) async fn consolidate_dependencies
(
oracle: Arc>,
boot: BootInfo,
- pre: PreState,
) -> Result<(), FaultProofProgramError>
where
P: PreimageOracleClient + Send + Sync + Debug + Clone,
H: HintWriterClient + Send + Sync + Debug + Clone,
{
- let provider = OracleInteropProvider::new(oracle, pre.clone());
+ let provider = OracleInteropProvider::new(oracle, boot.agreed_pre_state.clone());
info!(target: "client_interop", "Deriving local-safe headers from prestate");
// Ensure that the pre-state is a transition state.
- let PreState::TransitionState(ref transition_state) = pre else {
+ let PreState::TransitionState(ref transition_state) = boot.agreed_pre_state else {
return Err(FaultProofProgramError::StateTransitionFailed);
};
@@ -58,7 +57,10 @@ where
//
// TODO: This won't work if we replace blocks, `transition` doesn't allow replacement of pending
// progress just yet.
- let post = pre.transition(None).ok_or(FaultProofProgramError::StateTransitionFailed)?;
+ let post = boot
+ .agreed_pre_state
+ .transition(None)
+ .ok_or(FaultProofProgramError::StateTransitionFailed)?;
let post_commitment = post.hash();
// Ensure that the post-state matches the claimed post-state.
diff --git a/bin/client/src/interop/mod.rs b/bin/client/src/interop/mod.rs
index 73a550eae..86d96bdc7 100644
--- a/bin/client/src/interop/mod.rs
+++ b/bin/client/src/interop/mod.rs
@@ -2,7 +2,6 @@
use alloc::sync::Arc;
use alloy_primitives::B256;
-use alloy_rlp::Decodable;
use consolidate::consolidate_dependencies;
use core::fmt::Debug;
use kona_driver::DriverError;
@@ -13,7 +12,6 @@ use kona_proof_interop::{BootInfo, PreState, INVALID_TRANSITION_HASH, TRANSITION
use thiserror::Error;
use tracing::{error, info};
use transition::sub_transition;
-use util::read_raw_pre_state;
pub(crate) mod consolidate;
pub(crate) mod transition;
@@ -69,7 +67,7 @@ where
};
// If the pre state is invalid, short-circuit and check if the post-state claim is also invalid.
- if boot.agreed_pre_state == INVALID_TRANSITION_HASH &&
+ if boot.agreed_pre_state_commitment == INVALID_TRANSITION_HASH &&
boot.claimed_post_state == INVALID_TRANSITION_HASH
{
info!(target: "client_interop", "Invalid pre and post state, short-circuiting.");
@@ -78,20 +76,18 @@ where
// Load in the agreed pre-state from the preimage oracle in order to determine the active
// sub-problem.
- let pre = PreState::decode(&mut read_raw_pre_state(oracle.as_ref(), &boot).await?.as_ref())
- .map_err(FaultProofProgramError::RLPDecodingError)?;
- match pre {
+ match boot.agreed_pre_state {
PreState::SuperRoot(_) => {
// If the pre-state is a super root, the first sub-problem is always selected.
- sub_transition(oracle, handle_register, boot, pre).await
+ sub_transition(oracle, handle_register, boot).await
}
PreState::TransitionState(ref transition_state) => {
// If the pre-state is a transition state, the sub-problem is selected based on the
// current step.
if transition_state.step < TRANSITION_STATE_MAX_STEPS {
- sub_transition(oracle, handle_register, boot, pre).await
+ sub_transition(oracle, handle_register, boot).await
} else {
- consolidate_dependencies(oracle, boot, pre).await
+ consolidate_dependencies(oracle, boot).await
}
}
}
diff --git a/bin/client/src/interop/transition.rs b/bin/client/src/interop/transition.rs
index 5db3f30b5..136207f0b 100644
--- a/bin/client/src/interop/transition.rs
+++ b/bin/client/src/interop/transition.rs
@@ -31,31 +31,42 @@ pub(crate) async fn sub_transition(
>,
>,
boot: BootInfo,
- pre: PreState,
) -> Result<(), FaultProofProgramError>
where
P: PreimageOracleClient + Send + Sync + Debug + Clone,
H: HintWriterClient + Send + Sync + Debug + Clone,
{
// Check if we can short-circuit the transition, if we are within padding.
- if let PreState::TransitionState(ref transition_state) = pre {
+ if let PreState::TransitionState(ref transition_state) = boot.agreed_pre_state {
if transition_state.step >= transition_state.pre_state.output_roots.len() as u64 {
info!(
target: "interop_client",
"No derivation/execution required, transition state is already saturated."
);
- return transition_and_check(pre, None, boot.claimed_post_state, None);
+ return transition_and_check(boot.agreed_pre_state, None, boot.claimed_post_state, None);
}
}
// Fetch the L2 block hash of the current safe head.
- let safe_head_hash = fetch_l2_safe_head_hash(oracle.as_ref(), &pre).await?;
+ let safe_head_hash = fetch_l2_safe_head_hash(oracle.as_ref(), &boot.agreed_pre_state).await?;
+
+ // Determine the active L2 chain ID and the fetch rollup configuration.
+ let active_l2_chain_id = boot
+ .agreed_pre_state
+ .active_l2_chain_id()
+ .ok_or(FaultProofProgramError::StateTransitionFailed)?;
+ let rollup_config = boot
+ .rollup_configs
+ .get(&active_l2_chain_id)
+ .cloned()
+ .map(Arc::new)
+ .ok_or(FaultProofProgramError::StateTransitionFailed)?;
// Instantiate the L1 EL + CL provider and the L2 EL provider.
let mut l1_provider = OracleL1ChainProvider::new(boot.l1_head, oracle.clone());
let mut l2_provider =
- OracleL2ChainProvider::new(safe_head_hash, boot.rollup_config.clone(), oracle.clone());
+ OracleL2ChainProvider::new(safe_head_hash, rollup_config.clone(), oracle.clone());
let beacon = OracleBlobProvider::new(oracle.clone());
// Fetch the safe head's block header.
@@ -64,9 +75,8 @@ where
.map(|header| Sealed::new_unchecked(header, safe_head_hash))?;
// Translate the claimed timestamp to an L2 block number.
- let claimed_l2_block_number = boot.rollup_config.genesis.l2.number +
- ((boot.claimed_l2_timestamp - boot.rollup_config.genesis.l2_time) /
- boot.rollup_config.block_time);
+ let claimed_l2_block_number = rollup_config.genesis.l2.number +
+ ((boot.claimed_l2_timestamp - rollup_config.genesis.l2_time) / rollup_config.block_time);
// If the claimed L2 block number is less than the safe head of the L2 chain, the claim is
// invalid.
@@ -79,7 +89,7 @@ where
safe = safe_head.number
);
return Err(FaultProofProgramError::InvalidClaim(
- boot.agreed_pre_state,
+ boot.agreed_pre_state_commitment,
boot.claimed_post_state,
));
}
@@ -95,29 +105,34 @@ where
// Create a new derivation driver with the given boot information and oracle.
let cursor =
- new_pipeline_cursor(&boot.rollup_config, safe_head, &mut l1_provider, &mut l2_provider)
+ new_pipeline_cursor(rollup_config.as_ref(), safe_head, &mut l1_provider, &mut l2_provider)
.await?;
l2_provider.set_cursor(cursor.clone());
- let cfg = Arc::new(boot.rollup_config.clone());
let pipeline = OraclePipeline::new(
- cfg.clone(),
+ rollup_config.clone(),
cursor.clone(),
oracle.clone(),
beacon,
l1_provider.clone(),
l2_provider.clone(),
);
- let executor = KonaExecutor::new(&cfg, l2_provider.clone(), l2_provider, handle_register, None);
+ let executor = KonaExecutor::new(
+ rollup_config.as_ref(),
+ l2_provider.clone(),
+ l2_provider,
+ handle_register,
+ None,
+ );
let mut driver = Driver::new(cursor, executor, pipeline);
// Run the derivation pipeline until we are able to produce the output root of the claimed
// L2 block.
- match driver.advance_to_target(&boot.rollup_config, Some(claimed_l2_block_number)).await {
+ match driver.advance_to_target(rollup_config.as_ref(), Some(claimed_l2_block_number)).await {
Ok((safe_head, output_root)) => {
let optimistic_block = OptimisticBlock::new(safe_head.block_info.hash, output_root);
transition_and_check(
- pre,
+ boot.agreed_pre_state,
Some(optimistic_block),
boot.claimed_post_state,
Some((boot.claimed_l2_timestamp, safe_head.block_info.timestamp)),
@@ -137,14 +152,12 @@ where
"Exhausted data source; Transitioning to invalid state."
);
- if boot.claimed_post_state == INVALID_TRANSITION_HASH {
- Ok(())
- } else {
- Err(FaultProofProgramError::InvalidClaim(
+ (boot.claimed_post_state == INVALID_TRANSITION_HASH).then_some(()).ok_or(
+ FaultProofProgramError::InvalidClaim(
INVALID_TRANSITION_HASH,
boot.claimed_post_state,
- ))
- }
+ ),
+ )
}
Err(e) => {
error!(
diff --git a/bin/client/src/interop/util.rs b/bin/client/src/interop/util.rs
index 2098fc2db..b42764350 100644
--- a/bin/client/src/interop/util.rs
+++ b/bin/client/src/interop/util.rs
@@ -1,39 +1,12 @@
//! Utilities for the interop proof program
use alloc::string::ToString;
-use alloy_primitives::{Bytes, B256};
+use alloy_primitives::B256;
use kona_preimage::{errors::PreimageOracleError, CommsClient, PreimageKey, PreimageKeyType};
use kona_proof::errors::OracleProviderError;
-use kona_proof_interop::{BootInfo, HintType, PreState};
+use kona_proof_interop::{HintType, PreState};
-/// Reads the raw pre-state from the preimage oracle.
-pub(crate) async fn read_raw_pre_state(
- caching_oracle: &O,
- boot_info: &BootInfo,
-) -> Result
-where
- O: CommsClient,
-{
- caching_oracle
- .write(&HintType::AgreedPreState.encode_with(&[boot_info.agreed_pre_state.as_ref()]))
- .await
- .map_err(OracleProviderError::Preimage)?;
- let pre = caching_oracle
- .get(PreimageKey::new(*boot_info.agreed_pre_state, PreimageKeyType::Keccak256))
- .await
- .map_err(OracleProviderError::Preimage)?;
-
- if pre.is_empty() {
- return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
- "Invalid pre-state preimage".to_string(),
- )));
- }
-
- Ok(Bytes::from(pre))
-}
-
-/// Fetches the safe head hash of the L2 chain based on the agreed upon L2 output root in the
-/// [BootInfo].
+/// Fetches the safe head hash of the L2 chain, using the active L2 chain in the [PreState].
pub(crate) async fn fetch_l2_safe_head_hash(
caching_oracle: &O,
pre: &PreState,
diff --git a/bin/client/src/single.rs b/bin/client/src/single.rs
index 71361639e..3a00a6fc5 100644
--- a/bin/client/src/single.rs
+++ b/bin/client/src/single.rs
@@ -55,18 +55,13 @@ where
////////////////////////////////////////////////////////////////
let oracle = Arc::new(CachingOracle::new(ORACLE_LRU_SIZE, oracle_client, hint_client));
- let boot = match BootInfo::load(oracle.as_ref()).await {
- Ok(boot) => Arc::new(boot),
- Err(e) => {
- error!(target: "client", "Failed to load boot info: {:?}", e);
- return Err(e.into());
- }
- };
- let safe_head_hash = fetch_safe_head_hash(oracle.as_ref(), boot.as_ref()).await?;
+ let boot = BootInfo::load(oracle.as_ref()).await?;
+ let rollup_config = Arc::new(boot.rollup_config);
+ let safe_head_hash = fetch_safe_head_hash(oracle.as_ref(), boot.agreed_l2_output_root).await?;
let mut l1_provider = OracleL1ChainProvider::new(boot.l1_head, oracle.clone());
let mut l2_provider =
- OracleL2ChainProvider::new(safe_head_hash, boot.rollup_config.clone(), oracle.clone());
+ OracleL2ChainProvider::new(safe_head_hash, rollup_config.clone(), oracle.clone());
let beacon = OracleBlobProvider::new(oracle.clone());
// Fetch the safe head's block header.
@@ -105,26 +100,32 @@ where
// Create a new derivation driver with the given boot information and oracle.
let cursor =
- new_pipeline_cursor(&boot.rollup_config, safe_head, &mut l1_provider, &mut l2_provider)
+ new_pipeline_cursor(rollup_config.as_ref(), safe_head, &mut l1_provider, &mut l2_provider)
.await?;
l2_provider.set_cursor(cursor.clone());
- let cfg = Arc::new(boot.rollup_config.clone());
let pipeline = OraclePipeline::new(
- cfg.clone(),
+ rollup_config.clone(),
cursor.clone(),
oracle.clone(),
beacon,
l1_provider.clone(),
l2_provider.clone(),
);
- let executor = KonaExecutor::new(&cfg, l2_provider.clone(), l2_provider, handle_register, None);
+ let executor = KonaExecutor::new(
+ rollup_config.as_ref(),
+ l2_provider.clone(),
+ l2_provider,
+ handle_register,
+ None,
+ );
let mut driver = Driver::new(cursor, executor, pipeline);
// Run the derivation pipeline until we are able to produce the output root of the claimed
// L2 block.
- let (safe_head, output_root) =
- driver.advance_to_target(&boot.rollup_config, Some(boot.claimed_l2_block_number)).await?;
+ let (safe_head, output_root) = driver
+ .advance_to_target(rollup_config.as_ref(), Some(boot.claimed_l2_block_number))
+ .await?;
////////////////////////////////////////////////////////////////
// EPILOGUE //
@@ -154,7 +155,7 @@ where
/// [BootInfo].
pub async fn fetch_safe_head_hash(
caching_oracle: &O,
- boot_info: &BootInfo,
+ agreed_l2_output_root: B256,
) -> Result
where
O: CommsClient,
@@ -163,7 +164,7 @@ where
HintType::StartingL2Output
.get_exact_preimage(
caching_oracle,
- boot_info.agreed_l2_output_root,
+ agreed_l2_output_root,
PreimageKeyType::Keccak256,
&mut output_preimage,
)
diff --git a/bin/host/src/interop/fetcher.rs b/bin/host/src/interop/fetcher.rs
index 67744dc1c..306c5407c 100644
--- a/bin/host/src/interop/fetcher.rs
+++ b/bin/host/src/interop/fetcher.rs
@@ -687,7 +687,7 @@ where
let mut l1_provider = OracleL1ChainProvider::new(l1_head, oracle.clone());
let mut l2_provider = OracleL2ChainProvider::new(
agreed_block_hash,
- rollup_config.as_ref().clone(),
+ rollup_config.clone(),
oracle.clone(),
);
let beacon = OracleBlobProvider::new(oracle.clone());
diff --git a/bin/host/src/interop/local_kv.rs b/bin/host/src/interop/local_kv.rs
index d9bcdeb0d..57de709fc 100644
--- a/bin/host/src/interop/local_kv.rs
+++ b/bin/host/src/interop/local_kv.rs
@@ -7,9 +7,10 @@ use anyhow::Result;
use kona_host::KeyValueStore;
use kona_preimage::PreimageKey;
use kona_proof_interop::boot::{
- L1_HEAD_KEY, L2_AGREED_PRE_STATE_KEY, L2_CHAIN_ID_KEY, L2_CLAIMED_POST_STATE_KEY,
- L2_CLAIMED_TIMESTAMP_KEY, L2_ROLLUP_CONFIG_KEY,
+ L1_HEAD_KEY, L2_AGREED_PRE_STATE_KEY, L2_CLAIMED_POST_STATE_KEY, L2_CLAIMED_TIMESTAMP_KEY,
+ L2_ROLLUP_CONFIG_KEY,
};
+use maili_registry::HashMap;
/// The default chain ID to use if none is provided.
pub(crate) const DEFAULT_CHAIN_ID: u64 = 0xbeef_babe;
@@ -37,14 +38,11 @@ impl KeyValueStore for LocalKeyValueStore {
}
L2_CLAIMED_POST_STATE_KEY => Some(self.cfg.claimed_l2_post_state.to_vec()),
L2_CLAIMED_TIMESTAMP_KEY => Some(self.cfg.claimed_l2_timestamp.to_be_bytes().to_vec()),
- L2_CHAIN_ID_KEY => Some(self.cfg.active_l2_chain_id().ok()?.to_be_bytes().to_vec()),
L2_ROLLUP_CONFIG_KEY => {
let rollup_configs = self.cfg.read_rollup_configs().ok()?;
- let active_rollup_config = rollup_configs
- .get(&self.cfg.active_l2_chain_id().ok()?)
- .cloned()
- .unwrap_or_default();
- let serialized = serde_json::to_vec(&active_rollup_config).ok()?;
+ let serialized =
+ serde_json::to_vec(&rollup_configs.into_iter().collect::>())
+ .ok()?;
Some(serialized)
}
_ => None,
diff --git a/crates/interop/Cargo.toml b/crates/interop/Cargo.toml
index 30d2ce8dc..ea4124463 100644
--- a/crates/interop/Cargo.toml
+++ b/crates/interop/Cargo.toml
@@ -27,6 +27,9 @@ op-alloy-consensus.workspace = true
# Arbitrary
arbitrary = { version = "1.4", features = ["derive"], optional = true }
+# Serde
+serde = { workspace = true, optional = true }
+
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
alloy-primitives = { workspace = true, features = ["rlp", "arbitrary"] }
@@ -35,3 +38,4 @@ rand.workspace = true
[features]
arbitrary = ["dep:arbitrary", "alloy-primitives/arbitrary"]
+serde = ["dep:serde", "alloy-primitives/serde"]
diff --git a/crates/interop/src/super_root.rs b/crates/interop/src/super_root.rs
index 8949b6062..3ce9c60ed 100644
--- a/crates/interop/src/super_root.rs
+++ b/crates/interop/src/super_root.rs
@@ -13,6 +13,7 @@ use alloy_rlp::{Buf, BufMut};
/// The [SuperRoot] is the snapshot of the superchain at a given timestamp.
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SuperRoot {
/// The timestamp of the superchain snapshot, in seconds.
pub timestamp: u64,
@@ -89,6 +90,7 @@ impl SuperRoot {
/// A wrapper around an output root hash with the chain ID it belongs to.
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OutputRootWithChain {
/// The chain ID of the output root.
pub chain_id: u64,
diff --git a/crates/proof-sdk/proof-interop/Cargo.toml b/crates/proof-sdk/proof-interop/Cargo.toml
index 4db5c0181..cdac72cf4 100644
--- a/crates/proof-sdk/proof-interop/Cargo.toml
+++ b/crates/proof-sdk/proof-interop/Cargo.toml
@@ -14,7 +14,7 @@ workspace = true
[dependencies]
# Workspace
kona-preimage.workspace = true
-kona-interop.workspace = true
+kona-interop = { workspace = true, features = ["serde"] }
kona-proof.workspace = true
kona-mpt.workspace = true
diff --git a/crates/proof-sdk/proof-interop/src/boot.rs b/crates/proof-sdk/proof-interop/src/boot.rs
index fc472a88d..29dbb465d 100644
--- a/crates/proof-sdk/proof-interop/src/boot.rs
+++ b/crates/proof-sdk/proof-interop/src/boot.rs
@@ -1,11 +1,17 @@
//! This module contains the prologue phase of the client program, pulling in the boot information
//! through the `PreimageOracle` ABI as local keys.
-use alloy_primitives::{B256, U256};
-use kona_preimage::{PreimageKey, PreimageOracleClient};
+use crate::{HintType, PreState};
+use alloc::{string::ToString, vec::Vec};
+use alloy_primitives::{Bytes, B256, U256};
+use alloy_rlp::Decodable;
+use kona_preimage::{
+ errors::PreimageOracleError, CommsClient, HintWriterClient, PreimageKey, PreimageKeyType,
+ PreimageOracleClient,
+};
use kona_proof::errors::OracleProviderError;
use maili_genesis::RollupConfig;
-use maili_registry::ROLLUP_CONFIGS;
+use maili_registry::{HashMap, ROLLUP_CONFIGS};
use serde::{Deserialize, Serialize};
use tracing::warn;
@@ -21,9 +27,6 @@ pub const L2_CLAIMED_POST_STATE_KEY: U256 = U256::from_be_slice(&[3]);
/// The local key ident for the L2 claim timestamp.
pub const L2_CLAIMED_TIMESTAMP_KEY: U256 = U256::from_be_slice(&[4]);
-/// The local key ident for the L2 chain ID.
-pub const L2_CHAIN_ID_KEY: U256 = U256::from_be_slice(&[5]);
-
/// The local key ident for the L2 rollup config.
pub const L2_ROLLUP_CONFIG_KEY: U256 = U256::from_be_slice(&[6]);
@@ -33,15 +36,15 @@ pub struct BootInfo {
/// The L1 head hash containing the safe L2 chain data that may reproduce the post-state claim.
pub l1_head: B256,
/// The agreed upon superchain pre-state commitment.
- pub agreed_pre_state: B256,
+ pub agreed_pre_state_commitment: B256,
+ /// The agreed upon superchain pre-state.
+ pub agreed_pre_state: PreState,
/// The claimed (disputed) superchain post-state commitment.
pub claimed_post_state: B256,
/// The L2 claim timestamp.
pub claimed_l2_timestamp: u64,
- /// The L2 chain ID.
- pub chain_id: u64,
/// The rollup config for the L2 chain.
- pub rollup_config: RollupConfig,
+ pub rollup_configs: HashMap,
}
impl BootInfo {
@@ -55,7 +58,7 @@ impl BootInfo {
/// - `Err(_)`: Failed to load the boot information.
pub async fn load(oracle: &O) -> Result
where
- O: PreimageOracleClient + Send,
+ O: PreimageOracleClient + HintWriterClient + Clone + Send,
{
let mut l1_head: B256 = B256::ZERO;
oracle
@@ -84,25 +87,29 @@ impl BootInfo {
.try_into()
.map_err(OracleProviderError::SliceConversion)?,
);
- let chain_id = u64::from_be_bytes(
- oracle
- .get(PreimageKey::new_local(L2_CHAIN_ID_KEY.to()))
- .await
- .map_err(OracleProviderError::Preimage)?
- .as_slice()
- .try_into()
- .map_err(OracleProviderError::SliceConversion)?,
- );
+
+ let agreed_pre_state =
+ PreState::decode(&mut read_raw_pre_state(oracle, l2_pre).await?.as_ref())
+ .map_err(OracleProviderError::Rlp)?;
+
+ let chain_ids: Vec<_> = match agreed_pre_state {
+ PreState::SuperRoot(ref super_root) => {
+ super_root.output_roots.iter().map(|r| r.chain_id).collect()
+ }
+ PreState::TransitionState(ref transition_state) => {
+ transition_state.pre_state.output_roots.iter().map(|r| r.chain_id).collect()
+ }
+ };
// Attempt to load the rollup config from the chain ID. If there is no config for the chain,
// fall back to loading the config from the preimage oracle.
- let rollup_config = if let Some(config) = ROLLUP_CONFIGS.get(&chain_id) {
- config.clone()
+ let rollup_configs = if chain_ids.iter().all(|id| ROLLUP_CONFIGS.contains_key(id)) {
+ chain_ids.iter().map(|id| (*id, ROLLUP_CONFIGS[id].clone())).collect()
} else {
warn!(
target: "boot-loader",
- "No rollup config found for chain ID {}, falling back to preimage oracle. This is insecure in production without additional validation!",
- chain_id
+ "No rollup config found for chain IDs {:?}, falling back to preimage oracle. This is insecure in production without additional validation!",
+ chain_ids
);
let ser_cfg = oracle
.get(PreimageKey::new_local(L2_ROLLUP_CONFIG_KEY.to()))
@@ -113,11 +120,37 @@ impl BootInfo {
Ok(Self {
l1_head,
- agreed_pre_state: l2_pre,
+ rollup_configs,
+ agreed_pre_state_commitment: l2_pre,
+ agreed_pre_state,
claimed_post_state: l2_post,
claimed_l2_timestamp: l2_claim_block,
- chain_id,
- rollup_config,
})
}
}
+
+/// Reads the raw pre-state from the preimage oracle.
+pub(crate) async fn read_raw_pre_state(
+ caching_oracle: &O,
+ agreed_pre_state_commitment: B256,
+) -> Result
+where
+ O: CommsClient,
+{
+ caching_oracle
+ .write(&HintType::AgreedPreState.encode_with(&[agreed_pre_state_commitment.as_ref()]))
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+ let pre = caching_oracle
+ .get(PreimageKey::new(*agreed_pre_state_commitment, PreimageKeyType::Keccak256))
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+
+ if pre.is_empty() {
+ return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
+ "Invalid pre-state preimage".to_string(),
+ )));
+ }
+
+ Ok(Bytes::from(pre))
+}
diff --git a/crates/proof-sdk/proof-interop/src/pre_state.rs b/crates/proof-sdk/proof-interop/src/pre_state.rs
index 386fd5558..bcc920a42 100644
--- a/crates/proof-sdk/proof-interop/src/pre_state.rs
+++ b/crates/proof-sdk/proof-interop/src/pre_state.rs
@@ -4,6 +4,7 @@ use alloc::vec::Vec;
use alloy_primitives::{b256, keccak256, Bytes, B256};
use alloy_rlp::{Buf, Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
use kona_interop::{OutputRootWithChain, SuperRoot, SUPER_ROOT_VERSION};
+use serde::{Deserialize, Serialize};
/// The current [TransitionState] encoding format version.
pub(crate) const TRANSITION_STATE_VERSION: u8 = 255;
@@ -19,7 +20,7 @@ pub const INVALID_TRANSITION_HASH: B256 =
/// [TransitionState]. The [SuperRoot] is the canonical state of the superchain, while the
/// [TransitionState] is a super-structure of the [SuperRoot] that represents the progress of a
/// pending superchain state transition from one [SuperRoot] to the next.
-#[derive(Debug, Clone, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
pub enum PreState {
/// The canonical state of the superchain.
@@ -36,6 +37,22 @@ impl PreState {
keccak256(&rlp_buf)
}
+ /// Returns the active L2 chain ID of the [PreState]. This is the chain ID of the output root
+ /// that is to be committed to in the next transition step, or `0xDEAD` if the [PreState]
+ /// has already been fully saturated.
+ pub fn active_l2_chain_id(&self) -> Option {
+ match self {
+ Self::SuperRoot(super_root) => {
+ super_root.output_roots.first().map(|output_root| output_root.chain_id)
+ }
+ Self::TransitionState(transition_state) => transition_state
+ .pre_state
+ .output_roots
+ .get(transition_state.step as usize)
+ .map(|output_root| output_root.chain_id),
+ }
+ }
+
/// Transitions to the next state, appending the [OptimisticBlock] to the pending progress.
pub fn transition(self, optimistic_block: Option) -> Option {
match self {
@@ -117,7 +134,7 @@ impl Decodable for PreState {
/// The [TransitionState] is a super-structure of the [SuperRoot] that represents the progress of a
/// pending superchain state transition from one [SuperRoot] to the next.
-#[derive(Debug, Clone, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
pub struct TransitionState {
/// The canonical pre-state super root commitment.
@@ -204,7 +221,9 @@ impl Decodable for TransitionState {
}
/// A wrapper around a pending output root hash with the block hash it commits to.
-#[derive(Default, Debug, Clone, Eq, PartialEq, RlpEncodable, RlpDecodable)]
+#[derive(
+ Default, Debug, Clone, Eq, PartialEq, RlpEncodable, RlpDecodable, Serialize, Deserialize,
+)]
#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
pub struct OptimisticBlock {
/// The block hash of the output root.
diff --git a/crates/proof-sdk/proof/src/l2/chain_provider.rs b/crates/proof-sdk/proof/src/l2/chain_provider.rs
index 20c211db9..2185abbc6 100644
--- a/crates/proof-sdk/proof/src/l2/chain_provider.rs
+++ b/crates/proof-sdk/proof/src/l2/chain_provider.rs
@@ -23,7 +23,7 @@ pub struct OracleL2ChainProvider {
/// The L2 safe head block hash.
l2_head: B256,
/// The rollup configuration.
- rollup_config: RollupConfig,
+ rollup_config: Arc,
/// The preimage oracle client.
oracle: Arc,
/// The derivation pipeline cursor
@@ -32,7 +32,7 @@ pub struct OracleL2ChainProvider {
impl OracleL2ChainProvider {
/// Creates a new [OracleL2ChainProvider] with the given boot information and oracle client.
- pub const fn new(l2_head: B256, rollup_config: RollupConfig, oracle: Arc) -> Self {
+ pub const fn new(l2_head: B256, rollup_config: Arc, oracle: Arc) -> Self {
Self { l2_head, rollup_config, oracle, cursor: None }
}