diff --git a/Cargo.lock b/Cargo.lock
index c84679a3..0823dd0e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2577,14 +2577,19 @@ dependencies = [
name = "kona-proof-interop"
version = "0.1.1"
dependencies = [
+ "alloy-consensus",
+ "alloy-eips",
"alloy-primitives",
"alloy-rlp",
"arbitrary",
+ "async-trait",
"kona-interop",
+ "kona-mpt",
"kona-preimage",
"kona-proof",
"maili-genesis",
"maili-registry",
+ "op-alloy-consensus",
"rand",
"serde",
"serde_json",
diff --git a/bin/client/src/interop/consolidate.rs b/bin/client/src/interop/consolidate.rs
index 89571882..14cbe282 100644
--- a/bin/client/src/interop/consolidate.rs
+++ b/bin/client/src/interop/consolidate.rs
@@ -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
(
+ oracle: Arc>,
+ 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::>();
+
+ 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(())
+}
diff --git a/bin/client/src/interop/mod.rs b/bin/client/src/interop/mod.rs
index 369e9eef..18b9305b 100644
--- a/bin/client/src/interop/mod.rs
+++ b/bin/client/src/interop/mod.rs
@@ -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};
@@ -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;
@@ -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
{
@@ -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
}
}
}
diff --git a/bin/client/src/interop/transition.rs b/bin/client/src/interop/transition.rs
index c8ad0deb..e69bca7c 100644
--- a/bin/client/src/interop/transition.rs
+++ b/bin/client/src/interop/transition.rs
@@ -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);
diff --git a/bin/host/src/interop/fetcher.rs b/bin/host/src/interop/fetcher.rs
index 34020133..4db80f4e 100644
--- a/bin/host/src/interop/fetcher.rs
+++ b/bin/host/src/interop/fetcher.rs
@@ -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
@@ -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 = 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';
diff --git a/crates/interop/src/errors.rs b/crates/interop/src/errors.rs
index 3a78f073..05e1e9c7 100644
--- a/crates/interop/src/errors.rs
+++ b/crates/interop/src/errors.rs
@@ -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;
@@ -8,7 +9,7 @@ use thiserror::Error;
///
/// [MessageGraph]: crate::MessageGraph
#[derive(Debug, Clone, PartialEq, Eq, Error)]
-pub enum MessageGraphError {
+pub enum MessageGraphError {
/// Dependency set is impossibly empty
#[error("Dependency set is impossibly empty")]
EmptyDependencySet,
@@ -32,27 +33,13 @@ pub enum MessageGraphError {
InvalidMessages(Vec),
/// Interop provider error
#[error("Interop provider: {0}")]
- InteropProviderError(#[from] InteropProviderError),
+ InteropProviderError(#[from] E),
}
/// A [Result] alias for the [MessageGraphError] type.
-pub type MessageGraphResult = core::result::Result;
-
-/// 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 = core::result::Result;
+#[allow(type_alias_bounds)]
+pub type MessageGraphResult =
+ core::result::Result>;
/// An error type for the [SuperRoot] struct's serialization and deserialization.
///
diff --git a/crates/interop/src/graph.rs b/crates/interop/src/graph.rs
index 97e0648b..b6868b54 100644
--- a/crates/interop/src/graph.rs
+++ b/crates/interop/src/graph.rs
@@ -47,7 +47,10 @@ where
/// blocks and searching for [ExecutingMessage]s.
///
/// [ExecutingMessage]: crate::ExecutingMessage
- pub async fn derive(blocks: &[(u64, Sealed)], provider: P) -> MessageGraphResult {
+ pub async fn derive(
+ blocks: &[(u64, Sealed)],
+ provider: P,
+ ) -> MessageGraphResult {
info!(
target: "message-graph",
"Deriving message graph from {} blocks.",
@@ -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."
@@ -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());
@@ -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.
diff --git a/crates/interop/src/lib.rs b/crates/interop/src/lib.rs
index be4da320..c5fada5f 100644
--- a/crates/interop/src/lib.rs
+++ b/crates/interop/src/lib.rs
@@ -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};
diff --git a/crates/interop/src/test_util.rs b/crates/interop/src/test_util.rs
index ab6a1627..604c7337 100644
--- a/crates/interop/src/test_util.rs
+++ b/crates/interop/src/test_util.rs
@@ -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};
@@ -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 {
+ async fn header_by_number(&self, chain_id: u64, number: u64) -> Result {
Ok(self
.headers
.get(&chain_id)
@@ -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 {
- 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> {
+ ) -> Result, Self::Error> {
Ok(self.receipts.get(&chain_id).and_then(|receipts| receipts.get(&number)).unwrap().clone())
}
@@ -65,7 +57,7 @@ impl InteropProvider for MockInteropProvider {
&self,
chain_id: u64,
block_hash: B256,
- ) -> InteropProviderResult> {
+ ) -> Result, Self::Error> {
Ok(self
.receipts
.get(&chain_id)
diff --git a/crates/interop/src/traits.rs b/crates/interop/src/traits.rs
index dde157ae..b6d52355 100644
--- a/crates/interop/src/traits.rs
+++ b/crates/interop/src/traits.rs
@@ -1,33 +1,33 @@
//! Traits for the `kona-interop` crate.
-use crate::errors::InteropProviderResult;
use alloc::{boxed::Box, vec::Vec};
use alloy_consensus::Header;
use alloy_primitives::B256;
use async_trait::async_trait;
+use core::{error::Error, fmt::Display};
use op_alloy_consensus::OpReceiptEnvelope;
/// Describes the interface of the interop data provider. This provider is multiplexed over several
/// chains, with each method consuming a chain ID to determine the target chain.
#[async_trait]
pub trait InteropProvider {
- /// Fetch a [Header] by its number.
- async fn header_by_number(&self, chain_id: u64, number: u64) -> InteropProviderResult;
+ /// The error type for the provider.
+ type Error: Error + Display;
- /// Fetch a [Header] by its hash.
- async fn header_by_hash(&self, chain_id: u64, hash: B256) -> InteropProviderResult;
+ /// Fetch a [Header] by its number.
+ async fn header_by_number(&self, chain_id: u64, number: u64) -> Result;
/// Fetch all receipts for a given block by number.
async fn receipts_by_number(
&self,
chain_id: u64,
number: u64,
- ) -> InteropProviderResult>;
+ ) -> Result, Self::Error>;
/// Fetch all receipts for a given block by hash.
async fn receipts_by_hash(
&self,
chain_id: u64,
block_hash: B256,
- ) -> InteropProviderResult>;
+ ) -> Result, Self::Error>;
}
diff --git a/crates/proof-sdk/proof-interop/Cargo.toml b/crates/proof-sdk/proof-interop/Cargo.toml
index 88007279..393b772c 100644
--- a/crates/proof-sdk/proof-interop/Cargo.toml
+++ b/crates/proof-sdk/proof-interop/Cargo.toml
@@ -16,6 +16,7 @@ workspace = true
kona-preimage.workspace = true
kona-interop.workspace = true
kona-proof.workspace = true
+kona-mpt.workspace = true
# Maili
maili-registry.workspace = true
@@ -24,11 +25,17 @@ maili-genesis = { workspace = true, features = ["serde"] }
# Alloy
alloy-rlp.workspace = true
alloy-primitives.workspace = true
+alloy-consensus.workspace = true
+alloy-eips.workspace = true
+
+# OP Alloy
+op-alloy-consensus.workspace = true
# General
serde.workspace = true
tracing.workspace = true
serde_json.workspace = true
+async-trait.workspace = true
# Arbitrary
arbitrary = { version = "1.4", features = ["derive"], optional = true }
diff --git a/crates/proof-sdk/proof-interop/src/hint.rs b/crates/proof-sdk/proof-interop/src/hint.rs
index c1a1d43f..83886030 100644
--- a/crates/proof-sdk/proof-interop/src/hint.rs
+++ b/crates/proof-sdk/proof-interop/src/hint.rs
@@ -58,6 +58,8 @@ pub enum HintType {
L2BlockHeader,
/// A hint that specifies the transactions of a layer 2 block.
L2Transactions,
+ /// A hint that specifies the receipts of a layer 2 block.
+ L2Receipts,
/// A hint that specifies the code of a contract on layer 2.
L2Code,
/// A hint that specifies the preimage of the agreed upon pre-state claim.
@@ -97,6 +99,7 @@ impl TryFrom<&str> for HintType {
"l1-precompile" => Ok(Self::L1Precompile),
"l2-block-header" => Ok(Self::L2BlockHeader),
"l2-transactions" => Ok(Self::L2Transactions),
+ "l2-receipts" => Ok(Self::L2Receipts),
"l2-code" => Ok(Self::L2Code),
"agreed-pre-state" => Ok(Self::AgreedPreState),
"l2-output-root" => Ok(Self::L2OutputRoot),
@@ -119,6 +122,7 @@ impl From for &str {
HintType::L1Precompile => "l1-precompile",
HintType::L2BlockHeader => "l2-block-header",
HintType::L2Transactions => "l2-transactions",
+ HintType::L2Receipts => "l2-receipts",
HintType::L2Code => "l2-code",
HintType::AgreedPreState => "agreed-pre-state",
HintType::L2OutputRoot => "l2-output-root",
diff --git a/crates/proof-sdk/proof-interop/src/lib.rs b/crates/proof-sdk/proof-interop/src/lib.rs
index 8a3ec5c6..96e3d1a1 100644
--- a/crates/proof-sdk/proof-interop/src/lib.rs
+++ b/crates/proof-sdk/proof-interop/src/lib.rs
@@ -15,5 +15,8 @@ pub use pre_state::{
mod hint;
pub use hint::{Hint, HintType};
+mod provider;
+pub use provider::OracleInteropProvider;
+
pub mod boot;
pub use boot::BootInfo;
diff --git a/crates/proof-sdk/proof-interop/src/provider.rs b/crates/proof-sdk/proof-interop/src/provider.rs
new file mode 100644
index 00000000..5f9e7a06
--- /dev/null
+++ b/crates/proof-sdk/proof-interop/src/provider.rs
@@ -0,0 +1,179 @@
+//! [InteropProvider] trait implementation using a [CommsClient] data source.
+
+use crate::{HintType, PreState};
+use alloc::{boxed::Box, sync::Arc, vec::Vec};
+use alloy_consensus::Header;
+use alloy_eips::eip2718::Decodable2718;
+use alloy_primitives::B256;
+use alloy_rlp::Decodable;
+use async_trait::async_trait;
+use kona_interop::InteropProvider;
+use kona_mpt::{OrderedListWalker, TrieNode, TrieProvider};
+use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType};
+use kona_proof::errors::OracleProviderError;
+use op_alloy_consensus::OpReceiptEnvelope;
+
+/// A [CommsClient] backed [InteropProvider] implementation.
+#[derive(Debug, Clone)]
+pub struct OracleInteropProvider {
+ /// The oracle client.
+ oracle: Arc,
+ /// The [PreState] for the current program execution.
+ pre_state: PreState,
+}
+
+impl OracleInteropProvider
+where
+ T: CommsClient + Send + Sync,
+{
+ /// Creates a new [OracleInteropProvider] with the given oracle client and [PreState].
+ pub const fn new(oracle: Arc, pre_state: PreState) -> Self {
+ Self { oracle, pre_state }
+ }
+
+ /// Fetch the [Header] for the block with the given hash.
+ pub async fn header_by_hash(
+ &self,
+ chain_id: u64,
+ block_hash: B256,
+ ) -> Result::Error> {
+ self.oracle
+ .write(
+ &HintType::L2BlockHeader
+ .encode_with(&[block_hash.as_slice(), chain_id.to_be_bytes().as_ref()]),
+ )
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+
+ let header_rlp = self
+ .oracle
+ .get(PreimageKey::new(*block_hash, PreimageKeyType::Keccak256))
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+
+ Header::decode(&mut header_rlp.as_ref()).map_err(OracleProviderError::Rlp)
+ }
+
+ /// Fetch the [OpReceiptEnvelope]s for the block with the given hash.
+ async fn derive_receipts(
+ &self,
+ chain_id: u64,
+ block_hash: B256,
+ header: &Header,
+ ) -> Result, ::Error> {
+ // Send a hint for the block's receipts, and walk through the receipts trie in the header to
+ // verify them.
+ self.oracle
+ .write(
+ &HintType::L2Receipts
+ .encode_with(&[block_hash.as_ref(), chain_id.to_be_bytes().as_slice()]),
+ )
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+ let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)
+ .map_err(OracleProviderError::TrieWalker)?;
+
+ // Decode the receipts within the receipts trie.
+ let receipts = trie_walker
+ .into_iter()
+ .map(|(_, rlp)| {
+ let envelope = OpReceiptEnvelope::decode_2718(&mut rlp.as_ref())?;
+ Ok(envelope)
+ })
+ .collect::, _>>()
+ .map_err(OracleProviderError::Rlp)?;
+
+ Ok(receipts)
+ }
+}
+
+#[async_trait]
+impl InteropProvider for OracleInteropProvider
+where
+ T: CommsClient + Send + Sync,
+{
+ type Error = OracleProviderError;
+
+ /// Fetch a [Header] by its number.
+ async fn header_by_number(&self, chain_id: u64, number: u64) -> Result {
+ // Find the safe head for the given chain ID.
+ //
+ // TODO: Deduplicate + cache safe head lookups.
+ let pre_state = match &self.pre_state {
+ PreState::SuperRoot(super_root) => super_root,
+ PreState::TransitionState(transition_state) => &transition_state.pre_state,
+ };
+ let output = pre_state
+ .output_roots
+ .iter()
+ .find(|o| o.chain_id == chain_id)
+ .ok_or(OracleProviderError::UnknownChainId(chain_id))?;
+ self.oracle
+ .write(&HintType::L2OutputRoot.encode_with(&[
+ output.output_root.as_slice(),
+ output.chain_id.to_be_bytes().as_slice(),
+ ]))
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+ let output_preimage = self
+ .oracle
+ .get(PreimageKey::new(*output.output_root, PreimageKeyType::Keccak256))
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+ let safe_head_hash =
+ output_preimage[96..128].try_into().map_err(OracleProviderError::SliceConversion)?;
+
+ // Fetch the starting block header.
+ let mut header = self.header_by_hash(chain_id, safe_head_hash).await?;
+
+ // Check if the block number is in range. If not, we can fail early.
+ if number > header.number {
+ return Err(OracleProviderError::BlockNumberPastHead(number, header.number));
+ }
+
+ // Walk back the block headers to the desired block number.
+ while header.number > number {
+ header = self.header_by_hash(chain_id, header.parent_hash).await?;
+ }
+
+ Ok(header)
+ }
+
+ /// Fetch all receipts for a given block by number.
+ async fn receipts_by_number(
+ &self,
+ chain_id: u64,
+ number: u64,
+ ) -> Result, Self::Error> {
+ let header = self.header_by_number(chain_id, number).await?;
+ self.derive_receipts(chain_id, header.hash_slow(), &header).await
+ }
+
+ /// Fetch all receipts for a given block by hash.
+ async fn receipts_by_hash(
+ &self,
+ chain_id: u64,
+ block_hash: B256,
+ ) -> Result, Self::Error> {
+ let header = self.header_by_hash(chain_id, block_hash).await?;
+ self.derive_receipts(chain_id, block_hash, &header).await
+ }
+}
+
+impl TrieProvider for OracleInteropProvider
+where
+ T: CommsClient + Send + Sync + Clone,
+{
+ type Error = OracleProviderError;
+
+ fn trie_node_by_hash(&self, key: B256) -> Result {
+ kona_proof::block_on(async move {
+ let trie_node_rlp = self
+ .oracle
+ .get(PreimageKey::new(*key, PreimageKeyType::Keccak256))
+ .await
+ .map_err(OracleProviderError::Preimage)?;
+ TrieNode::decode(&mut trie_node_rlp.as_ref()).map_err(OracleProviderError::Rlp)
+ })
+ }
+}
diff --git a/crates/proof-sdk/proof/src/errors.rs b/crates/proof-sdk/proof/src/errors.rs
index bc734ab8..3b013acf 100644
--- a/crates/proof-sdk/proof/src/errors.rs
+++ b/crates/proof-sdk/proof/src/errors.rs
@@ -15,7 +15,7 @@ pub enum OracleProviderError {
BlockNumberPastHead(u64, u64),
/// Preimage oracle error.
#[error("Preimage oracle error: {0}")]
- Preimage(PreimageOracleError),
+ Preimage(#[from] PreimageOracleError),
/// List walker error.
#[error("Trie walker error: {0}")]
TrieWalker(OrderedListWalkerError),
@@ -34,6 +34,9 @@ pub enum OracleProviderError {
/// Serde error.
#[error("Serde error: {0}")]
Serde(serde_json::Error),
+ /// Unknown Chain ID
+ #[error("Unknown chain ID: {0}")]
+ UnknownChainId(u64),
}
impl From for PipelineErrorKind {
diff --git a/crates/proof-sdk/proof/src/l1/chain_provider.rs b/crates/proof-sdk/proof/src/l1/chain_provider.rs
index 15842ff8..f9166982 100644
--- a/crates/proof-sdk/proof/src/l1/chain_provider.rs
+++ b/crates/proof-sdk/proof/src/l1/chain_provider.rs
@@ -77,7 +77,7 @@ impl ChainProvider for OracleL1ChainProvider {
let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)
.map_err(OracleProviderError::TrieWalker)?;
- // Decode the receipts within the transactions trie.
+ // Decode the receipts within the receipts trie.
let receipts = trie_walker
.into_iter()
.map(|(_, rlp)| {