Skip to content

Commit

Permalink
feat(client): Superchain Consolidation (#1004)
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby authored Feb 5, 2025
1 parent ca0a44f commit eaec362
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 38 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bin/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ kona-std-fpvm-proc.workspace = true
# Maili
maili-protocol.workspace = true
maili-genesis = { workspace = true, features = ["serde"] }
maili-registry.workspace = true

# Alloy
alloy-rlp.workspace = true
Expand Down
63 changes: 47 additions & 16 deletions bin/client/src/interop/consolidate.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Consolidation phase of the interop proof program.
use super::FaultProofProgramError;
use crate::interop::util::fetch_output_block_hash;
use alloc::{sync::Arc, vec::Vec};
use core::fmt::Debug;
use kona_interop::MessageGraph;
use kona_preimage::{HintWriterClient, PreimageOracleClient};
use kona_proof::CachingOracle;
use kona_proof_interop::{BootInfo, OracleInteropProvider, PreState};
use revm::primitives::HashMap;
use kona_proof::{l2::OracleL2ChainProvider, CachingOracle};
use kona_proof_interop::{
BootInfo, HintType, OracleInteropProvider, PreState, SuperchainConsolidator,
};
use maili_registry::{HashMap, ROLLUP_CONFIGS};
use tracing::info;

/// Executes the consolidation phase of the interop proof with the given [PreimageOracleClient] and
Expand All @@ -19,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 All @@ -38,25 +40,54 @@ where
.pending_progress
.iter()
.zip(transition_state.pre_state.output_roots.iter())
.map(|(optimistic_block, pre_state)| (pre_state.chain_id, optimistic_block.block_hash))
.map(|(optimistic_block, pre_state)| (pre_state, optimistic_block.block_hash))
.collect::<HashMap<_, _>>();

let mut headers = Vec::with_capacity(block_hashes.len());
for (chain_id, block_hash) in block_hashes {
let header = provider.header_by_hash(chain_id, block_hash).await?;
headers.push((chain_id, header.seal(block_hash)));
let mut l2_providers = HashMap::default();
for (pre, block_hash) in block_hashes {
// Fetch the safe head's block hash for the given L2 chain ID.
let safe_head_hash =
fetch_output_block_hash(oracle.as_ref(), pre.output_root, pre.chain_id).await?;

// Send hints for the L2 block data in the pending progress. This is an important step,
// because non-canonical blocks within the pending progress will not be able to be fetched
// by the host through the traditional means. If the block is determined to not be canonical
// 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.
HintType::L2BlockData
.with_data(&[
safe_head_hash.as_slice(),
block_hash.as_slice(),
pre.chain_id.to_be_bytes().as_slice(),
])
.send(oracle.as_ref())
.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)
.or_else(|| boot.rollup_configs.get(&pre.chain_id))
.ok_or(FaultProofProgramError::MissingRollupConfig(pre.chain_id))?;

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());

// TODO: Re-execution w/ bad blocks. Not complete, we just panic if any deps are invalid atm.
let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
graph.resolve().await.unwrap();
// Consolidate the superchain
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(*rich_output.output_root, PreimageKeyType::Keccak256))
.get(PreimageKey::new_keccak256(*output_root))
.await
.map_err(OracleProviderError::Preimage)?;

Expand Down
22 changes: 11 additions & 11 deletions crates/interop/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use tracing::{info, warn};
///
/// [MessageIdentifier]: crate::MessageIdentifier
#[derive(Debug)]
pub struct MessageGraph<P> {
pub struct MessageGraph<'a, P> {
/// The horizon timestamp is the highest timestamp of all blocks containing [ExecutingMessage]s
/// within the graph.
///
Expand All @@ -36,10 +36,10 @@ pub struct MessageGraph<P> {
messages: Vec<EnrichedExecutingMessage>,
/// The data provider for the graph. Required for fetching headers, receipts and remote
/// messages within history during resolution.
provider: P,
provider: &'a P,
}

impl<P> MessageGraph<P>
impl<'a, P> MessageGraph<'a, P>
where
P: InteropProvider,
{
Expand All @@ -49,7 +49,7 @@ where
/// [ExecutingMessage]: crate::ExecutingMessage
pub async fn derive(
blocks: &[(u64, Sealed<Header>)],
provider: P,
provider: &'a P,
) -> MessageGraphResult<Self, P> {
info!(
target: "message-graph",
Expand Down Expand Up @@ -249,7 +249,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
graph.resolve().await.unwrap();
}

Expand All @@ -270,7 +270,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
graph.resolve().await.unwrap();
}

Expand All @@ -283,7 +283,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
assert_eq!(graph.resolve().await.unwrap_err(), MessageGraphError::InvalidMessages(vec![2]));
}

Expand All @@ -296,7 +296,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
assert_eq!(graph.resolve().await.unwrap_err(), MessageGraphError::InvalidMessages(vec![2]));
}

Expand All @@ -309,7 +309,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
assert_eq!(graph.resolve().await.unwrap_err(), MessageGraphError::InvalidMessages(vec![2]));
}

Expand All @@ -322,7 +322,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
assert_eq!(graph.resolve().await.unwrap_err(), MessageGraphError::InvalidMessages(vec![2]));
}

Expand All @@ -341,7 +341,7 @@ mod test {

let (headers, provider) = superchain.build();

let graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
let graph = MessageGraph::derive(headers.as_slice(), &provider).await.unwrap();
assert_eq!(graph.resolve().await.unwrap_err(), MessageGraphError::InvalidMessages(vec![2]));
}
}
2 changes: 1 addition & 1 deletion crates/interop/src/super_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl SuperRoot {
}

/// A wrapper around an output root hash with the chain ID it belongs to.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OutputRootWithChain {
Expand Down
4 changes: 4 additions & 0 deletions crates/proof-sdk/proof-interop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ kona-preimage.workspace = true
kona-interop = { workspace = true, features = ["serde"] }
kona-proof.workspace = true
kona-mpt.workspace = true
kona-executor.workspace = true

# Maili
maili-registry.workspace = true
Expand All @@ -27,16 +28,19 @@ alloy-rlp.workspace = true
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-eips.workspace = true
alloy-rpc-types-engine.workspace = true

# OP Alloy
op-alloy-consensus.workspace = true
op-alloy-rpc-types-engine.workspace = true

# General
serde.workspace = true
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
Loading

0 comments on commit eaec362

Please sign in to comment.