Skip to content

Commit

Permalink
feat(client): Superchain Consolidation
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Feb 5, 2025
1 parent 3b93c13 commit eec12a9
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 47 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 20 additions & 19 deletions bin/client/src/interop/consolidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::interop::util::fetch_output_block_hash;
use alloc::{sync::Arc, vec::Vec};
use core::fmt::Debug;
use kona_preimage::{HintWriterClient, PreimageOracleClient};
use kona_proof::{errors::OracleProviderError, l2::OracleL2ChainProvider, CachingOracle};
use kona_proof::{l2::OracleL2ChainProvider, CachingOracle};
use kona_proof_interop::{
BootInfo, HintType, OracleInteropProvider, PreState, SuperchainConsolidator,
};
Expand All @@ -21,13 +21,13 @@ use tracing::info;
/// [OptimisticBlock]: kona_proof_interop::OptimisticBlock
pub(crate) async fn consolidate_dependencies<P, H>(
oracle: Arc<CachingOracle<P, H>>,
boot: BootInfo,
mut boot: BootInfo,
) -> Result<(), FaultProofProgramError>
where
P: PreimageOracleClient + Send + Sync + Debug + Clone,
H: HintWriterClient + Send + Sync + Debug + Clone,
{
let provider = OracleInteropProvider::new(oracle, boot.agreed_pre_state.clone());
let provider = OracleInteropProvider::new(oracle.clone(), boot.agreed_pre_state.clone());

info!(target: "client_interop", "Deriving local-safe headers from prestate");

Expand Down Expand Up @@ -56,37 +56,38 @@ where
// by the host, it will re-execute it and store the required preimages to complete
// deposit-only re-execution. If the block is determined to be canonical, the host will
// no-op, and fetch preimages through the traditional route as needed.
oracle
.write(&HintType::L2BlockData.encode_with(&[
HintType::L2BlockData
.with_data(&[
safe_head_hash.as_slice(),
block_hash.as_slice(),
pre.chain_id.to_be_bytes().as_slice(),
]))
.await
.map_err(OracleProviderError::Preimage)?;
])
.send(oracle.as_ref())
.await?;

let header = interop_provider.header_by_hash(pre.chain_id, block_hash).await?;
let header = provider.header_by_hash(pre.chain_id, block_hash).await?;
headers.push((pre.chain_id, header.seal(block_hash)));

let rollup_config =
ROLLUP_CONFIGS.get(&pre.chain_id).cloned().expect("TODO: Handle gracefully");
let rollup_config = ROLLUP_CONFIGS
.get(&pre.chain_id)
.or_else(|| boot.rollup_configs.get(&pre.chain_id))
.ok_or(FaultProofProgramError::MissingRollupConfig(pre.chain_id))?;

let provider = OracleL2ChainProvider::new(safe_head_hash, rollup_config, oracle.clone());
let mut provider = OracleL2ChainProvider::new(
safe_head_hash,
Arc::new(rollup_config.clone()),
oracle.clone(),
);
provider.set_chain_id(Some(pre.chain_id));
l2_providers.insert(pre.chain_id, provider);
}

info!(target: "client_interop", "Loaded {} local-safe headers", headers.len());

// Consolidate the superchain
SuperchainConsolidator::new(&mut pre, interop_provider, l2_providers, headers)
.consolidate()
.await
.expect("TODO: Handle gracefully");
SuperchainConsolidator::new(&mut boot, provider, l2_providers, headers).consolidate().await?;

// Transition to the Super Root at the next timestamp.
//
// TODO: This won't work if we replace blocks, `transition` doesn't allow replacement of pending
// progress just yet.
let post = boot
.agreed_pre_state
.transition(None)
Expand Down
14 changes: 11 additions & 3 deletions bin/client/src/interop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use kona_driver::DriverError;
use kona_executor::{ExecutorError, KonaHandleRegister};
use kona_preimage::{HintWriterClient, PreimageOracleClient};
use kona_proof::{errors::OracleProviderError, l2::OracleL2ChainProvider, CachingOracle};
use kona_proof_interop::{BootInfo, PreState, INVALID_TRANSITION_HASH, TRANSITION_STATE_MAX_STEPS};
use kona_proof_interop::{
BootInfo, ConsolidationError, PreState, INVALID_TRANSITION_HASH, TRANSITION_STATE_MAX_STEPS,
};
use thiserror::Error;
use tracing::{error, info};
use transition::sub_transition;
Expand All @@ -25,16 +27,22 @@ pub enum FaultProofProgramError {
InvalidClaim(B256, B256),
/// An error occurred in the Oracle provider.
#[error(transparent)]
OracleProviderError(#[from] OracleProviderError),
OracleProvider(#[from] OracleProviderError),
/// An error occurred in the driver.
#[error(transparent)]
Driver(#[from] DriverError<ExecutorError>),
/// An error occurred during RLP decoding.
#[error("RLP decoding error: {0}")]
RLPDecodingError(alloy_rlp::Error),
Rlp(alloy_rlp::Error),
/// State transition failed.
#[error("Critical state transition failure")]
StateTransitionFailed,
/// Missing a rollup configuration.
#[error("Missing rollup configuration for chain ID {0}")]
MissingRollupConfig(u64),
/// Consolidation error.
#[error(transparent)]
Consolidation(#[from] ConsolidationError),
}

/// Executes the interop fault proof program with the given [PreimageOracleClient] and
Expand Down
24 changes: 17 additions & 7 deletions bin/client/src/interop/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
use alloc::string::ToString;
use alloy_primitives::B256;
use kona_preimage::{errors::PreimageOracleError, CommsClient, PreimageKey, PreimageKeyType};
use kona_preimage::{errors::PreimageOracleError, CommsClient, PreimageKey};
use kona_proof::errors::OracleProviderError;
use kona_proof_interop::{HintType, PreState};

/// Fetches the safe head hash of the L2 chain, using the active L2 chain in the [PreState].
/// Fetches the safe head hash of the L2 chain based on the agreed upon L2 output root in the
/// [PreState].
pub(crate) async fn fetch_l2_safe_head_hash<O>(
caching_oracle: &O,
pre: &PreState,
Expand All @@ -30,15 +31,24 @@ where
}
};

fetch_output_block_hash(caching_oracle, rich_output.output_root, rich_output.chain_id).await
}

/// Fetches the block hash that the passed output root commits to.
pub(crate) async fn fetch_output_block_hash<O>(
caching_oracle: &O,
output_root: B256,
chain_id: u64,
) -> Result<B256, OracleProviderError>
where
O: CommsClient,
{
HintType::L2OutputRoot
.with_data(&[
rich_output.output_root.as_slice(),
rich_output.chain_id.to_be_bytes().as_slice(),
])
.with_data(&[output_root.as_slice(), chain_id.to_be_bytes().as_slice()])
.send(caching_oracle)
.await?;
let output_preimage = caching_oracle
.get(PreimageKey::new(*output_root, PreimageKeyType::Keccak256))
.get(PreimageKey::new_keccak256(*output_root))
.await
.map_err(OracleProviderError::Preimage)?;

Expand Down
1 change: 1 addition & 0 deletions crates/proof-sdk/proof-interop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ tracing.workspace = true
serde_json.workspace = true
async-trait.workspace = true
spin.workspace = true
thiserror.workspace = true

# Arbitrary
arbitrary = { version = "1.4", features = ["derive"], optional = true }
Expand Down
57 changes: 40 additions & 17 deletions crates/proof-sdk/proof-interop/src/consolidation.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
//! Interop dependency resolution and consolidation logic.
use crate::{OptimisticBlock, OracleInteropProvider, PreState};
use crate::{BootInfo, OptimisticBlock, OracleInteropProvider, PreState};
use alloc::{boxed::Box, vec::Vec};
use alloy_consensus::{Header, Sealed};
use alloy_primitives::Sealable;
use alloy_rpc_types_engine::PayloadAttributes;
use kona_executor::StatelessL2BlockExecutor;
use kona_executor::{ExecutorError, StatelessL2BlockExecutor};
use kona_interop::{MessageGraph, MessageGraphError};
use kona_mpt::OrderedListWalker;
use kona_preimage::CommsClient;
use kona_proof::{errors::OracleProviderError, l2::OracleL2ChainProvider};
use maili_registry::{HashMap, ROLLUP_CONFIGS};
use op_alloy_consensus::OpTxType;
use op_alloy_rpc_types_engine::OpPayloadAttributes;
use thiserror::Error;
use tracing::{error, info};

/// The [SuperchainConsolidator] holds a [MessageGraph] and is responsible for recursively
Expand All @@ -24,8 +25,8 @@ pub struct SuperchainConsolidator<'a, C>
where
C: CommsClient,
{
/// The [PreState] being operated on.
pre_state: &'a mut PreState,
/// The [BootInfo] of the program.
boot_info: &'a mut BootInfo,
/// The [OracleInteropProvider] used for the message graph.
interop_provider: OracleInteropProvider<C>,
/// The [OracleL2ChainProvider]s used for re-execution of invalid blocks, keyed by chain ID.
Expand All @@ -40,27 +41,27 @@ where
{
/// Creates a new [SuperchainConsolidator] with the given providers and [Header]s.
pub const fn new(
pre_state: &'a mut PreState,
boot_info: &'a mut BootInfo,
interop_provider: OracleInteropProvider<C>,
l2_providers: HashMap<u64, OracleL2ChainProvider<C>>,
headers: Vec<(u64, Sealed<Header>)>,
) -> Self {
Self { pre_state, interop_provider, l2_providers, headers }
Self { boot_info, interop_provider, l2_providers, headers }
}

/// Recursively consolidates the dependencies of the blocks within the [MessageGraph].
///
/// This method will recurse until all invalid cross-chain dependencies have been resolved,
/// re-executing deposit-only blocks for chains with invalid dependencies as needed.
pub async fn consolidate(&mut self) -> Result<(), MessageGraphError<OracleProviderError>> {
pub async fn consolidate(&mut self) -> Result<(), ConsolidationError> {
info!(target: "superchain_consolidator", "Consolidating superchain");

match self.consolidate_once().await {
Ok(()) => {
info!(target: "superchain_consolidator", "Superchain consolidation complete");
Ok(())
}
Err(MessageGraphError::InvalidMessages(_)) => {
Err(ConsolidationError::MessageGraph(MessageGraphError::InvalidMessages(_))) => {
// If invalid messages are still present in the graph, recurse.
Box::pin(self.consolidate()).await
}
Expand All @@ -78,15 +79,15 @@ where
/// 2. Resolve the [MessageGraph].
/// 3. If any invalid messages are found, re-execute the bad block(s) only deposit transactions,
/// and bubble up the error.
async fn consolidate_once(&mut self) -> Result<(), MessageGraphError<OracleProviderError>> {
async fn consolidate_once(&mut self) -> Result<(), ConsolidationError> {
// Derive the message graph from the current set of block headers.
let graph = MessageGraph::derive(self.headers.as_slice(), &self.interop_provider).await?;

// Attempt to resolve the message graph. If there were any invalid messages found, we must
// initiate a re-execution of the original block, with only deposit transactions.
if let Err(MessageGraphError::InvalidMessages(chain_ids)) = graph.resolve().await {
self.re_execute_deposit_only(&chain_ids).await?;
return Err(MessageGraphError::InvalidMessages(chain_ids));
return Err(MessageGraphError::InvalidMessages(chain_ids).into());
}

Ok(())
Expand All @@ -97,7 +98,7 @@ where
async fn re_execute_deposit_only(
&mut self,
chain_ids: &[u64],
) -> Result<(), MessageGraphError<OracleProviderError>> {
) -> Result<(), ConsolidationError> {
for chain_id in chain_ids {
// Find the optimistic block header for the chain ID.
let header = self
Expand Down Expand Up @@ -147,10 +148,10 @@ where
};

// Fetch the rollup config + provider for the current chain ID.
//
// TODO: Rollup cfg will require some reworking of the interop boot info to pass all of
// the rollup cfgs in.
let rollup_config = ROLLUP_CONFIGS.get(chain_id).expect("TODO: Handle gracefully");
let rollup_config = ROLLUP_CONFIGS
.get(chain_id)
.or_else(|| self.boot_info.rollup_configs.get(chain_id))
.ok_or(ConsolidationError::MissingRollupConfig(*chain_id))?;
let l2_provider = self.l2_providers.get(chain_id).expect("TODO: Handle gracefully");

// Create a new stateless L2 block executor for the current chain.
Expand All @@ -169,8 +170,10 @@ where
let new_output_root = executor.compute_output_root().unwrap();

// Replace the original optimistic block with the deposit only block.
let PreState::TransitionState(ref mut transition_state) = self.pre_state else {
panic!("SuperchainConsolidator received invalid PreState variant");
let PreState::TransitionState(ref mut transition_state) =
self.boot_info.agreed_pre_state
else {
return Err(ConsolidationError::InvalidPreStateVariant);
};
let original_optimistic_block = transition_state
.pending_progress
Expand All @@ -186,3 +189,23 @@ where
Ok(())
}
}

/// An error type for the [SuperchainConsolidator] struct.
#[derive(Debug, Error)]
pub enum ConsolidationError {
/// An invalid pre-state variant was passed to the consolidator.
#[error("Invalid PreState variant")]
InvalidPreStateVariant,
/// Missing a rollup configuration.
#[error("Missing rollup configuration for chain ID {0}")]
MissingRollupConfig(u64),
/// An error occurred during consolidation.
#[error(transparent)]
MessageGraph(#[from] MessageGraphError<OracleProviderError>),
/// An error occurred during execution.
#[error(transparent)]
Executor(#[from] ExecutorError),
/// An error occurred during RLP decoding.
#[error(transparent)]
OracleProvider(#[from] OracleProviderError),
}
2 changes: 1 addition & 1 deletion crates/proof-sdk/proof-interop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ pub mod boot;
pub use boot::BootInfo;

mod consolidation;
pub use consolidation::SuperchainConsolidator;
pub use consolidation::{ConsolidationError, SuperchainConsolidator};

0 comments on commit eec12a9

Please sign in to comment.