Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): Superchain Consolidation #1004

Merged
merged 2 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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