Skip to content

Commit

Permalink
feat(client): Interop consolidation sub-problem
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Jan 19, 2025
1 parent 8e25874 commit eb4856d
Show file tree
Hide file tree
Showing 16 changed files with 336 additions and 63 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.

50 changes: 50 additions & 0 deletions bin/client/src/interop/consolidate.rs
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
//! Consolidation phase of the interop proof program.
use super::FaultProofProgramError;
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::{OracleInteropProvider, PreState};
use revm::primitives::HashMap;

/// Executes the consolidation phase of the interop proof with the given [PreimageOracleClient] and
/// [HintWriterClient].
///
/// This phase is responsible for checking the dependencies between [OptimisticBlock]s in the
/// superchain and ensuring that all dependencies are satisfied.
///
/// [OptimisticBlock]: kona_proof_interop::OptimisticBlock
pub(crate) async fn consolidate_dependencies<P, H>(
oracle: Arc<CachingOracle<P, H>>,
pre: PreState,
) -> Result<(), FaultProofProgramError>
where
P: PreimageOracleClient + Send + Sync + Debug + Clone,
H: HintWriterClient + Send + Sync + Debug + Clone,
{
let provider = OracleInteropProvider::new(oracle, pre.clone());

// Ensure that the pre-state is a transition state.
let PreState::TransitionState(transition_state) = pre else {
return Err(FaultProofProgramError::StateTransitionFailed);
};

let block_hashes = transition_state
.pending_progress
.iter()
.zip(transition_state.pre_state.output_roots)
.map(|(optimistic_block, pre_state)| (pre_state.chain_id, 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 graph = MessageGraph::derive(headers.as_slice(), provider).await.unwrap();
graph.resolve().await.unwrap();

Ok(())
}
10 changes: 6 additions & 4 deletions bin/client/src/interop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use alloc::sync::Arc;
use alloy_primitives::B256;
use alloy_rlp::Decodable;
use consolidate::consolidate_dependencies;
use core::fmt::Debug;
use kona_driver::DriverError;
use kona_executor::{ExecutorError, KonaHandleRegister};
Expand All @@ -11,6 +12,7 @@ use kona_proof::{errors::OracleProviderError, l2::OracleL2ChainProvider, Caching
use kona_proof_interop::{BootInfo, PreState, INVALID_TRANSITION_HASH, TRANSITION_STATE_MAX_STEPS};
use thiserror::Error;
use tracing::{error, info};
use transition::sub_transition;
use util::read_raw_pre_state;

pub(crate) mod consolidate;
Expand Down Expand Up @@ -66,7 +68,7 @@ where
}
};

// If the pre state is invalid, short-circuit and check if the post-state is also invalid.
// 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 &&
boot.claimed_post_state == INVALID_TRANSITION_HASH
{
Expand All @@ -81,15 +83,15 @@ where
match pre {
PreState::SuperRoot(_) => {
// If the pre-state is a super root, the first sub-problem is always selected.
transition::sub_transition(oracle, handle_register, boot, pre).await
sub_transition(oracle, handle_register, boot, pre).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 {
transition::sub_transition(oracle, handle_register, boot, pre).await
sub_transition(oracle, handle_register, boot, pre).await
} else {
unimplemented!("Consolidation step")
consolidate_dependencies(oracle, pre).await
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion bin/client/src/interop/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ where
if transition_state.step >= transition_state.pre_state.output_roots.len() as u64 {
info!(
target: "interop_client",
"No state transition required, transition state is already saturated."
"No derivation/execution required, transition state is already saturated."
);

return transition_and_check(pre, None, boot.claimed_post_state);
Expand Down
49 changes: 45 additions & 4 deletions bin/host/src/interop/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,19 +256,31 @@ where
}
HintType::L2BlockHeader => {
// Validate the hint data length.
if hint_data.len() != 32 {
if hint_data.len() < 32 || hint_data.len() > 40 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the raw header from the L2 chain provider.
let hash: B256 = hint_data
let hash: B256 = hint_data[0..32]
.as_ref()
.try_into()
.map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?;

let active_l2_chain_id = if hint_data.len() == 40 {
u64::from_be_bytes(
hint_data[32..40]
.as_ref()
.try_into()
.map_err(|e| anyhow!("Failed to convert bytes to u64: {e}"))?,
)
} else {
self.active_l2_chain_id
};

let raw_header: Bytes = self
.l2_providers
.get(&self.active_l2_chain_id)
.ok_or(anyhow!("No active L2 chain ID"))?
.get(&active_l2_chain_id)
.ok_or(anyhow!("No provider for active L2 chain ID"))?
.client()
.request("debug_getRawHeader", [hash])
.await
Expand Down Expand Up @@ -322,6 +334,35 @@ where
_ => anyhow::bail!("Only BlockTransactions::Hashes are supported."),
};
}
HintType::L2Receipts => {
// Validate the hint data length.
if hint_data.len() != 40 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the receipts from the L1 chain provider and store the receipts within the
// key-value store.
let hash: B256 = hint_data[0..32]
.as_ref()
.try_into()
.map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?;
let chain_id = u64::from_be_bytes(
hint_data[32..40]
.as_ref()
.try_into()
.map_err(|e| anyhow!("Failed to convert bytes to u64: {e}"))?,
);

let raw_receipts: Vec<Bytes> = self
.l2_providers
.get(&chain_id)
.ok_or(anyhow!("Provider for chain ID {chain_id} not found"))?
.client()
.request("debug_getRawReceipts", [hash])
.await
.map_err(|e| anyhow!(e))?;
self.store_trie_nodes(raw_receipts.as_slice()).await?;
}
HintType::L2Code => {
// geth hashdb scheme code hash key prefix
const CODE_PREFIX: u8 = b'c';
Expand Down
25 changes: 6 additions & 19 deletions crates/interop/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Error types for the `kona-interop` crate.
use crate::InteropProvider;
use alloc::vec::Vec;
use alloy_primitives::{Address, B256};
use thiserror::Error;
Expand All @@ -8,7 +9,7 @@ use thiserror::Error;
///
/// [MessageGraph]: crate::MessageGraph
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum MessageGraphError {
pub enum MessageGraphError<E> {
/// Dependency set is impossibly empty
#[error("Dependency set is impossibly empty")]
EmptyDependencySet,
Expand All @@ -32,27 +33,13 @@ pub enum MessageGraphError {
InvalidMessages(Vec<u64>),
/// Interop provider error
#[error("Interop provider: {0}")]
InteropProviderError(#[from] InteropProviderError),
InteropProviderError(#[from] E),
}

/// A [Result] alias for the [MessageGraphError] type.
pub type MessageGraphResult<T> = core::result::Result<T, MessageGraphError>;

/// An error type for the [InteropProvider] trait.
///
/// [InteropProvider]: crate::InteropProvider
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum InteropProviderError {
/// Unknown Chain ID
#[error("Unknown Chain ID")]
UnknownChainId,
/// Not found
#[error("Not found")]
NotFound,
}

/// A [Result] alias for the [InteropProviderError] type.
pub type InteropProviderResult<T> = core::result::Result<T, InteropProviderError>;
#[allow(type_alias_bounds)]
pub type MessageGraphResult<T, P: InteropProvider> =
core::result::Result<T, MessageGraphError<P::Error>>;

/// An error type for the [SuperRoot] struct's serialization and deserialization.
///
Expand Down
11 changes: 7 additions & 4 deletions crates/interop/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ where
/// blocks and searching for [ExecutingMessage]s.
///
/// [ExecutingMessage]: crate::ExecutingMessage
pub async fn derive(blocks: &[(u64, Sealed<Header>)], provider: P) -> MessageGraphResult<Self> {
pub async fn derive(
blocks: &[(u64, Sealed<Header>)],
provider: P,
) -> MessageGraphResult<Self, P> {
info!(
target: "message-graph",
"Deriving message graph from {} blocks.",
Expand Down Expand Up @@ -84,7 +87,7 @@ where
}

/// Checks the validity of all messages within the graph.
pub async fn resolve(mut self) -> MessageGraphResult<()> {
pub async fn resolve(mut self) -> MessageGraphResult<(), P> {
info!(
target: "message-graph",
"Checking the message graph for invalid messages."
Expand Down Expand Up @@ -120,7 +123,7 @@ where
/// Attempts to remove as many edges from the graph as possible by resolving the dependencies
/// of each message. If a message cannot be resolved, it is considered invalid. After this
/// function is called, any outstanding messages are invalid.
async fn reduce(&mut self) -> MessageGraphResult<()> {
async fn reduce(&mut self) -> MessageGraphResult<(), P> {
// Create a new vector to store invalid edges
let mut invalid_messages = Vec::with_capacity(self.messages.len());

Expand Down Expand Up @@ -155,7 +158,7 @@ where
async fn check_single_dependency(
&self,
message: &EnrichedExecutingMessage,
) -> MessageGraphResult<()> {
) -> MessageGraphResult<(), P> {
// ChainID Invariant: The chain id of the initiating message MUST be in the dependency set
// This is enforced implicitly by the graph constructor and the provider.

Expand Down
5 changes: 1 addition & 4 deletions crates/interop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ mod traits;
pub use traits::InteropProvider;

mod errors;
pub use errors::{
InteropProviderError, InteropProviderResult, MessageGraphError, MessageGraphResult,
SuperRootError, SuperRootResult,
};
pub use errors::{MessageGraphError, MessageGraphResult, SuperRootError, SuperRootResult};

mod super_root;
pub use super_root::{OutputRootWithChain, SuperRoot};
Expand Down
28 changes: 10 additions & 18 deletions crates/interop/src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
#![allow(missing_docs, unreachable_pub)]

use crate::{
errors::InteropProviderResult, traits::InteropProvider, ExecutingMessage, MessageIdentifier,
CROSS_L2_INBOX_ADDRESS,
};
use crate::{traits::InteropProvider, ExecutingMessage, MessageIdentifier, CROSS_L2_INBOX_ADDRESS};
use alloy_consensus::{Header, Receipt, ReceiptWithBloom, Sealed};
use alloy_primitives::{map::HashMap, Address, Bytes, Log, LogData, B256, U256};
use alloy_sol_types::{SolEvent, SolValue};
Expand All @@ -27,10 +24,16 @@ impl MockInteropProvider {
}
}

#[derive(thiserror::Error, Debug, Eq, PartialEq)]
#[error("Mock interop provider error")]
pub struct InteropProviderError;

#[async_trait]
impl InteropProvider for MockInteropProvider {
type Error = InteropProviderError;

/// Fetch a [Header] by its number.
async fn header_by_number(&self, chain_id: u64, number: u64) -> InteropProviderResult<Header> {
async fn header_by_number(&self, chain_id: u64, number: u64) -> Result<Header, Self::Error> {
Ok(self
.headers
.get(&chain_id)
Expand All @@ -40,23 +43,12 @@ impl InteropProvider for MockInteropProvider {
.clone())
}

/// Fetch a [Header] by its hash.
async fn header_by_hash(&self, chain_id: u64, hash: B256) -> InteropProviderResult<Header> {
Ok(self
.headers
.get(&chain_id)
.and_then(|headers| headers.values().find(|header| header.hash() == hash))
.unwrap()
.inner()
.clone())
}

/// Fetch all receipts for a given block by number.
async fn receipts_by_number(
&self,
chain_id: u64,
number: u64,
) -> InteropProviderResult<Vec<OpReceiptEnvelope>> {
) -> Result<Vec<OpReceiptEnvelope>, Self::Error> {
Ok(self.receipts.get(&chain_id).and_then(|receipts| receipts.get(&number)).unwrap().clone())
}

Expand All @@ -65,7 +57,7 @@ impl InteropProvider for MockInteropProvider {
&self,
chain_id: u64,
block_hash: B256,
) -> InteropProviderResult<Vec<OpReceiptEnvelope>> {
) -> Result<Vec<OpReceiptEnvelope>, Self::Error> {
Ok(self
.receipts
.get(&chain_id)
Expand Down
Loading

0 comments on commit eb4856d

Please sign in to comment.