Skip to content

Commit

Permalink
feat(client): Derivation integration
Browse files Browse the repository at this point in the history
Adds the `DerivationDriver` to the client program.
  • Loading branch information
clabby committed Jun 16, 2024
1 parent 2dab14e commit 38c83bc
Show file tree
Hide file tree
Showing 17 changed files with 374 additions and 50 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

23 changes: 17 additions & 6 deletions bin/host/src/fetcher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,15 @@ where
};

let mut kv_write_lock = self.kv_store.write().await;
kv_write_lock.set(hash, code.into());
kv_write_lock
.set(PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(), code.into());
}
HintType::StartingL2Output => {
const OUTPUT_ROOT_VERSION: u8 = 0;
const L2_TO_L1_MESSAGE_PASSER_ADDRESS: Address =
address!("4200000000000000000000000000000000000016");

if !hint_data.is_empty() {
if hint_data.len() != 32 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

Expand Down Expand Up @@ -388,8 +389,15 @@ where
raw_output[96..128].copy_from_slice(self.l2_head.as_ref());
let output_root = keccak256(raw_output);

if output_root.as_slice() != hint_data.as_ref() {
anyhow::bail!("Output root does not match L2 head.");
}

let mut kv_write_lock = self.kv_store.write().await;
kv_write_lock.set(output_root, raw_output.into());
kv_write_lock.set(
PreimageKey::new(*output_root, PreimageKeyType::Keccak256).into(),
raw_output.into(),
);
}
HintType::L2StateNode => {
if hint_data.len() != 32 {
Expand All @@ -410,7 +418,10 @@ where
.map_err(|e| anyhow!("Failed to fetch preimage: {e}"))?;

let mut kv_write_lock = self.kv_store.write().await;
kv_write_lock.set(hash, preimage.into());
kv_write_lock.set(
PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(),
preimage.into(),
);
}
HintType::L2AccountProof => {
if hint_data.len() != 8 + 20 {
Expand All @@ -422,7 +433,7 @@ where
.try_into()
.map_err(|e| anyhow!("Error converting hint data to u64: {e}"))?,
);
let address = Address::from_slice(&hint_data.as_ref()[8..]);
let address = Address::from_slice(&hint_data.as_ref()[8..28]);

let proof_response = self
.l2_provider
Expand All @@ -449,7 +460,7 @@ where
.try_into()
.map_err(|e| anyhow!("Error converting hint data to u64: {e}"))?,
);
let address = Address::from_slice(&hint_data.as_ref()[8..]);
let address = Address::from_slice(&hint_data.as_ref()[8..28]);
let slot = B256::from_slice(&hint_data.as_ref()[28..]);

let mut proof_response = self
Expand Down
2 changes: 1 addition & 1 deletion bin/host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ mod server;
mod types;
mod util;

#[tokio::main]
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
let cfg = HostCli::parse();
init_tracing_subscriber(cfg.v)?;
Expand Down
7 changes: 5 additions & 2 deletions bin/host/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ where
/// Starts the [PreimageServer] and waits for incoming requests.
pub async fn start(self) -> Result<()> {
// Create the futures for the oracle server and hint router.
let server_fut =
Self::start_oracle_server(self.kv_store, self.fetcher.clone(), self.oracle_server);
let server_fut = Self::start_oracle_server(
self.kv_store.clone(),
self.fetcher.clone(),
self.oracle_server,
);
let hinter_fut = Self::start_hint_router(self.hint_reader, self.fetcher);

// Spawn tasks for the futures and wait for them to complete.
Expand Down
6 changes: 4 additions & 2 deletions bin/host/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use kona_common::FileDescriptor;
use kona_preimage::PipeHandle;
use reqwest::Client;
use std::{fs::File, os::fd::AsRawFd};
use tempfile::tempfile;
use tokio::task::JoinHandle;

/// Parses a hint from a string.
Expand All @@ -34,7 +33,10 @@ pub(crate) fn parse_hint(s: &str) -> Result<(HintType, Bytes)> {

/// Creates two temporary files that are connected by a pipe.
pub(crate) fn create_temp_files() -> Result<(File, File)> {
let (read, write) = (tempfile().map_err(|e| anyhow!(e))?, tempfile().map_err(|e| anyhow!(e))?);
let (read, write) = (
tempfile::tempfile().map_err(|e| anyhow!(e))?,
tempfile::tempfile().map_err(|e| anyhow!(e))?,
);
Ok((read, write))
}

Expand Down
4 changes: 4 additions & 0 deletions bin/programs/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ revm = { workspace = true, features = ["optimism"] }
lru.workspace = true
spin.workspace = true
async-trait.workspace = true
tracing.workspace = true

# local
kona-common = { path = "../../../crates/common", version = "0.0.1" }
Expand All @@ -29,6 +30,9 @@ kona-primitives = { path = "../../../crates/primitives", version = "0.0.1" }
kona-mpt = { path = "../../../crates/mpt", version = "0.0.1" }
kona-derive = { path = "../../../crates/derive", version = "0.0.1" }

# external
tracing-subscriber = "0.3.18"

[dev-dependencies]
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.117"
4 changes: 2 additions & 2 deletions bin/programs/client/src/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub enum HintType {
impl HintType {
/// Encodes the hint type as a string.
pub fn encode_with(&self, data: &[&[u8]]) -> String {
let concatenated = data.iter().map(hex::encode).collect::<Vec<_>>().join(" ");
let concatenated = data.iter().map(hex::encode).collect::<Vec<_>>().join("");
alloc::format!("{} {}", self, concatenated)
}
}
Expand All @@ -55,7 +55,7 @@ impl TryFrom<&str> for HintType {
"l2-block-header" => Ok(HintType::L2BlockHeader),
"l2-transactions" => Ok(HintType::L2Transactions),
"l2-code" => Ok(HintType::L2Code),
"l2-output" => Ok(HintType::StartingL2Output),
"starting-l2-output" => Ok(HintType::StartingL2Output),
"l2-state-node" => Ok(HintType::L2StateNode),
"l2-account-proof" => Ok(HintType::L2AccountProof),
"l2-account-storage-proof" => Ok(HintType::L2AccountStorageProof),
Expand Down
4 changes: 3 additions & 1 deletion bin/programs/client/src/l1/blob_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use kona_preimage::{HintWriterClient, PreimageKey, PreimageKeyType, PreimageOrac
use kona_primitives::BlockInfo;

/// An oracle-backed blob provider.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct OracleBlobProvider {
oracle: Arc<CachingOracle>,
}
Expand Down Expand Up @@ -70,6 +70,8 @@ impl OracleBlobProvider {
blob[(i as usize) << 5..(i as usize + 1) << 5].copy_from_slice(field_element.as_ref());
}

tracing::info!(target: "client_oracle", "Retrieved blob {blob_hash:?} from the oracle.");

Ok(blob)
}
}
Expand Down
2 changes: 1 addition & 1 deletion bin/programs/client/src/l1/chain_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use kona_preimage::{HintWriterClient, PreimageKey, PreimageKeyType, PreimageOrac
use kona_primitives::BlockInfo;

/// The oracle-backed L1 chain provider for the client program.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct OracleL1ChainProvider {
/// The boot information
boot_info: Arc<BootInfo>,
Expand Down
180 changes: 180 additions & 0 deletions bin/programs/client/src/l1/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! Contains the [DerivationDriver] struct, which handles the [L2PayloadAttributes] derivation
//! process.
use super::{OracleBlobProvider, OracleL1ChainProvider};
use crate::{l2::OracleL2ChainProvider, BootInfo, CachingOracle, HintType, HINT_WRITER};
use alloc::sync::Arc;
use alloy_consensus::{Header, Sealed};
use anyhow::{anyhow, Result};
use core::fmt::Debug;
use kona_derive::{
pipeline::{DerivationPipeline, Pipeline, PipelineBuilder},
sources::EthereumDataSource,
stages::{
AttributesQueue, BatchQueue, ChannelBank, ChannelReader, FrameQueue, L1Retrieval,
L1Traversal, StatefulAttributesBuilder,
},
traits::{ChainProvider, L2ChainProvider},
};
use kona_mpt::TrieDBFetcher;
use kona_preimage::{HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient};
use kona_primitives::{BlockInfo, L2AttributesWithParent, L2BlockInfo};
use tracing::{info, warn};

/// An oracle-backed derivation pipeline.
pub type OraclePipeline =
DerivationPipeline<OracleAttributesQueue<OracleDataProvider>, OracleL2ChainProvider>;

/// An oracle-backed Ethereum data source.
pub type OracleDataProvider = EthereumDataSource<OracleL1ChainProvider, OracleBlobProvider>;

/// An oracle-backed payload attributes builder for the `AttributesQueue` stage of the derivation
/// pipeline.
pub type OracleAttributesBuilder =
StatefulAttributesBuilder<OracleL1ChainProvider, OracleL2ChainProvider>;

/// An oracle-backed attributes queue for the derivation pipeline.
pub type OracleAttributesQueue<DAP> = AttributesQueue<
BatchQueue<
ChannelReader<
ChannelBank<FrameQueue<L1Retrieval<DAP, L1Traversal<OracleL1ChainProvider>>>>,
>,
OracleL2ChainProvider,
>,
OracleAttributesBuilder,
>;

/// The [DerivationDriver] struct is responsible for handling the [L2PayloadAttributes] derivation
/// process.
///
/// It contains an inner [OraclePipeline] that is used to derive the attributes, backed by
/// oracle-based data sources.
#[derive(Debug)]
pub struct DerivationDriver {
/// The current L2 safe head.
l2_safe_head: L2BlockInfo,
/// The header of the L2 safe head.
l2_safe_head_header: Sealed<Header>,
/// The inner pipeline.
pipeline: OraclePipeline,
}

impl DerivationDriver {
/// Returns the current L2 safe head block information.
pub fn l2_safe_head(&self) -> &L2BlockInfo {
&self.l2_safe_head
}

/// Returns the header of the current L2 safe head.
pub fn l2_safe_head_header(&self) -> &Sealed<Header> {
&self.l2_safe_head_header
}

/// Creates a new [DerivationDriver] with the given configuration, blob provider, and chain
/// providers.
///
/// ## Takes
/// - `cfg`: The rollup configuration.
/// - `blob_provider`: The blob provider.
/// - `chain_provider`: The L1 chain provider.
/// - `l2_chain_provider`: The L2 chain provider.
///
/// ## Returns
/// - A new [DerivationDriver] instance.
pub async fn new(
boot_info: &BootInfo,
caching_oracle: &CachingOracle,
blob_provider: OracleBlobProvider,
mut chain_provider: OracleL1ChainProvider,
mut l2_chain_provider: OracleL2ChainProvider,
) -> Result<Self> {
let cfg = Arc::new(boot_info.rollup_config.clone());

// Fetch the startup information.
let (l1_origin, l2_safe_head, l2_safe_head_header) = Self::find_startup_info(
caching_oracle,
boot_info,
&mut chain_provider,
&mut l2_chain_provider,
)
.await?;

// Construct the pipeline.
let attributes = StatefulAttributesBuilder::new(
cfg.clone(),
l2_chain_provider.clone(),
chain_provider.clone(),
);
let dap = EthereumDataSource::new(chain_provider.clone(), blob_provider, &cfg);
let pipeline = PipelineBuilder::new()
.rollup_config(cfg)
.dap_source(dap)
.l2_chain_provider(l2_chain_provider)
.chain_provider(chain_provider)
.builder(attributes)
.origin(l1_origin)
.build();

Ok(Self { l2_safe_head, l2_safe_head_header, pipeline })
}

/// Produces the disputed [L2AttributesWithParent] payload, directly after the starting L2
/// output root passed through the [BootInfo].
pub async fn produce_disputed_payload(&mut self) -> Result<L2AttributesWithParent> {
// As we start the safe head at the disputed block's parent, we step the pipeline until the
// first attributes are produced. All batches at and before the safe head will be
// dropped, so the first payload will always be the disputed one.
let mut attributes = None;
while attributes.is_none() {
match self.pipeline.step(self.l2_safe_head).await {
Ok(_) => info!(target: "client_derivation_driver", "Stepped derivation pipeline"),
Err(e) => {
warn!(target: "client_derivation_driver", "Failed to step derivation pipeline: {:?}", e)
}
}

attributes = self.pipeline.next_attributes();
}

Ok(attributes.expect("Must be some"))
}

/// Finds the startup information for the derivation pipeline.
///
/// ## Takes
/// - `caching_oracle`: The caching oracle.
/// - `boot_info`: The boot information.
/// - `chain_provider`: The L1 chain provider.
/// - `l2_chain_provider`: The L2 chain provider.
///
/// ## Returns
/// - A tuple containing the L1 origin block information and the L2 safe head information.
async fn find_startup_info(
caching_oracle: &CachingOracle,
boot_info: &BootInfo,
chain_provider: &mut OracleL1ChainProvider,
l2_chain_provider: &mut OracleL2ChainProvider,
) -> Result<(BlockInfo, L2BlockInfo, Sealed<Header>)> {
// Find the initial safe head, based off of the starting L2 block number in the boot info.
HINT_WRITER
.write(&HintType::StartingL2Output.encode_with(&[boot_info.l2_output_root.as_ref()]))
.await?;
let mut output_preimage = [0u8; 128];
caching_oracle
.get_exact(
PreimageKey::new(*boot_info.l2_output_root, PreimageKeyType::Keccak256),
&mut output_preimage,
)
.await?;

let safe_hash =
output_preimage[96..128].try_into().map_err(|_| anyhow!("Invalid L2 output root"))?;
let safe_header = l2_chain_provider.header_by_hash(safe_hash)?;
let safe_head_info = l2_chain_provider.l2_block_info_by_number(safe_header.number).await?;

let l1_origin =
chain_provider.block_info_by_number(safe_head_info.l1_origin.number).await?;

Ok((l1_origin, safe_head_info, Sealed::new_unchecked(safe_header, safe_hash)))
}
}
6 changes: 6 additions & 0 deletions bin/programs/client/src/l1/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
//! Contains the L1 constructs of the client program.
mod driver;
pub use driver::{
DerivationDriver, OracleAttributesBuilder, OracleAttributesQueue, OracleDataProvider,
OraclePipeline,
};

mod blob_provider;
pub use blob_provider::OracleBlobProvider;

Expand Down
Loading

0 comments on commit 38c83bc

Please sign in to comment.