Skip to content

Commit

Permalink
feat(preimage): OracleServer + HintReader
Browse files Browse the repository at this point in the history
Adds the host end of the `PreimageOracle` ABI plumbing. This includes
two new traits:
* `PreimageOracleServer`
* `HintReaderServer`

as well as implementations of both of them that compliment the existing
client handles, the `OracleReader` and `HintWriter`.
  • Loading branch information
clabby committed Apr 6, 2024
1 parent 8f5c7ee commit 3d8cd85
Show file tree
Hide file tree
Showing 8 changed files with 792 additions and 42 deletions.
553 changes: 546 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crates/common/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ mod native_io {
.write(buf)
.map_err(|e| anyhow!("Error writing to buffer to file descriptor: {e}"))?;

// Reset the cursor back to 0 for the reader.
file.seek(SeekFrom::Start(0))
// Reset the cursor back to before the data we just wrote for the reader's consumption.
file.seek(SeekFrom::Current(-(buf.len() as i64)))
.map_err(|e| anyhow!("Failed to reset file cursor to 0: {e}"))?;

// forget the file descriptor so that the `Drop` impl doesn't close it.
Expand Down
1 change: 1 addition & 0 deletions crates/preimage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ kona-common = { path = "../common", version = "0.0.1" }
[dev-dependencies]
tokio = { version = "1.36.0", features = ["full"] }
tempfile = "3.10.0"
alloy-primitives = "0.7.0"
108 changes: 106 additions & 2 deletions crates/preimage/src/hint.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{traits::HintWriterClient, PipeHandle};
use alloc::vec;
use crate::{traits::HintWriterClient, HintReaderServer, PipeHandle};
use alloc::{string::String, vec};
use anyhow::Result;

/// A [HintWriter] is a high-level interface to the hint pipe. It provides a way to write hints to
Expand Down Expand Up @@ -36,3 +36,107 @@ impl HintWriterClient for HintWriter {
Ok(())
}
}

/// A [HintReader] is a router for hints sent by the [HintWriter] from the client program. It
/// provides a way for the host to prepare preimages for reading.
#[derive(Debug, Clone, Copy)]
pub struct HintReader {
pipe_handle: PipeHandle,
}

impl HintReader {
/// Create a new [HintReader] from a [PipeHandle].
pub fn new(pipe_handle: PipeHandle) -> Self {
Self { pipe_handle }
}
}

impl HintReaderServer for HintReader {
fn next_hint(&self, mut route_hint: impl FnMut(String) -> Result<()>) -> Result<()> {
// Read the length of the raw hint payload.
let mut len_buf = [0u8; 4];
self.pipe_handle.read_exact(&mut len_buf)?;
let len = u32::from_be_bytes(len_buf);

// Read the raw hint payload.
let mut payload = vec![0u8; len as usize];
self.pipe_handle.read_exact(payload.as_mut_slice())?;

// Route the hint
if let Err(e) = route_hint(
String::from_utf8(payload)
.map_err(|e| anyhow::anyhow!("Failed to decode hint payload: {e}"))?,
) {
// Write back on error to prevent blocking the client.
self.pipe_handle.write(&[0x00])?;
anyhow::bail!("Failed to handle hint: {e}");
}

// Write back an acknowledgement to the client to unblock their process.
self.pipe_handle.write(&[0x00])?;

Ok(())
}
}
#[cfg(test)]
mod test {
extern crate std;

use super::*;
use alloc::vec::Vec;
use kona_common::FileDescriptor;
use std::{fs::File, os::fd::AsRawFd};
use tempfile::tempfile;

/// Test struct containing the [HintReader] and [HintWriter]. The [File]s are stored in this
/// struct so that they are not dropped until the end of the test.
#[derive(Debug)]
struct ClientAndHost {
hint_writer: HintWriter,
hint_reader: HintReader,
_read_file: File,
_write_file: File,
}

/// Helper for creating a new [HintReader] and [HintWriter] for testing. The file channel is
/// over two temporary files.
fn client_and_host() -> ClientAndHost {
let (read_file, write_file) = (tempfile().unwrap(), tempfile().unwrap());
let (read_fd, write_fd) = (
FileDescriptor::Wildcard(read_file.as_raw_fd().try_into().unwrap()),
FileDescriptor::Wildcard(write_file.as_raw_fd().try_into().unwrap()),
);
let client_handle = PipeHandle::new(read_fd, write_fd);
let host_handle = PipeHandle::new(write_fd, read_fd);

let hint_writer = HintWriter::new(client_handle);
let hint_reader = HintReader::new(host_handle);

ClientAndHost { hint_writer, hint_reader, _read_file: read_file, _write_file: write_file }
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_hint_client_and_host() {
const MOCK_DATA: &str = "test-hint 0xfacade";

let sys = client_and_host();
let (hint_writer, hint_reader) = (sys.hint_writer, sys.hint_reader);

let client = tokio::task::spawn(async move { hint_writer.write(MOCK_DATA) });
let host = tokio::task::spawn(async move {
let mut v = Vec::new();
let route_hint = |hint: String| {
v.push(hint.clone());
Ok(())
};
hint_reader.next_hint(route_hint).unwrap();

assert_eq!(v.len(), 1);

v.remove(0)
});

let (_, h) = tokio::join!(client, host);
assert_eq!(h.unwrap(), MOCK_DATA);
}
}
28 changes: 26 additions & 2 deletions crates/preimage/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! the preimage oracle.
/// <https://specs.optimism.io/experimental/fault-proof/index.html#pre-image-key-types>
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[repr(u8)]
pub enum PreimageKeyType {
/// Local key types are local to a given instance of a fault-proof and context dependent.
Expand All @@ -23,6 +23,21 @@ pub enum PreimageKeyType {
Blob = 5,
}

impl TryFrom<u8> for PreimageKeyType {
type Error = anyhow::Error;

fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
1 => PreimageKeyType::Local,
2 => PreimageKeyType::Keccak256,
3 => PreimageKeyType::GlobalGeneric,
4 => PreimageKeyType::Sha256,
5 => PreimageKeyType::Blob,
_ => anyhow::bail!("Invalid preimage key type"),
})
}
}

/// A preimage key is a 32-byte value that identifies a preimage that may be fetched from the
/// oracle.
///
Expand All @@ -31,7 +46,7 @@ pub enum PreimageKeyType {
/// |---------|-------------|
/// | [0, 1) | Type byte |
/// | [1, 32) | Data |
#[derive(Debug, Default, Clone, Copy)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
pub struct PreimageKey {
data: [u8; 31],
key_type: PreimageKeyType,
Expand Down Expand Up @@ -69,6 +84,15 @@ impl From<PreimageKey> for [u8; 32] {
}
}

impl TryFrom<[u8; 32]> for PreimageKey {
type Error = anyhow::Error;

fn try_from(value: [u8; 32]) -> Result<Self, Self::Error> {
let key_type = PreimageKeyType::try_from(value[0])?;
Ok(Self::new(value, key_type))
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
6 changes: 3 additions & 3 deletions crates/preimage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ mod key;
pub use key::{PreimageKey, PreimageKeyType};

mod oracle;
pub use oracle::OracleReader;
pub use oracle::{OracleReader, OracleServer};

mod hint;
pub use hint::HintWriter;
pub use hint::{HintReader, HintWriter};

mod pipe;
pub use pipe::PipeHandle;

mod traits;
pub use traits::{HintWriterClient, PreimageOracleClient};
pub use traits::{HintReaderServer, HintWriterClient, PreimageOracleClient, PreimageOracleServer};
102 changes: 79 additions & 23 deletions crates/preimage/src/oracle.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{traits::PreimageOracleClient, PipeHandle, PreimageKey};
use crate::{PipeHandle, PreimageKey, PreimageOracleClient, PreimageOracleServer};
use alloc::vec::Vec;
use anyhow::{bail, Result};

Expand All @@ -17,7 +17,7 @@ impl OracleReader {
/// Set the preimage key for the global oracle reader. This will overwrite any existing key, and
/// block until the host has prepared the preimage and responded with the length of the
/// preimage.
fn write_key(&mut self, key: PreimageKey) -> Result<usize> {
fn write_key(&self, key: PreimageKey) -> Result<usize> {
// Write the key to the host so that it can prepare the preimage.
let key_bytes: [u8; 32] = key.into();
self.pipe_handle.write(&key_bytes)?;
Expand All @@ -32,7 +32,7 @@ impl OracleReader {
impl PreimageOracleClient for OracleReader {
/// Get the data corresponding to the currently set key from the host. Return the data in a new
/// heap allocated `Vec<u8>`
fn get(&mut self, key: PreimageKey) -> Result<Vec<u8>> {
fn get(&self, key: PreimageKey) -> Result<Vec<u8>> {
let length = self.write_key(key)?;
let mut data_buffer = alloc::vec![0; length];

Expand All @@ -44,7 +44,7 @@ impl PreimageOracleClient for OracleReader {

/// Get the data corresponding to the currently set key from the host. Write the data into the
/// provided buffer
fn get_exact(&mut self, key: PreimageKey, buf: &mut [u8]) -> Result<()> {
fn get_exact(&self, key: PreimageKey, buf: &mut [u8]) -> Result<()> {
// Write the key to the host and read the length of the preimage.
let length = self.write_key(key)?;

Expand All @@ -59,33 +59,68 @@ impl PreimageOracleClient for OracleReader {
}
}

/// An [OracleServer] is a router for the host to serve data back to the client [OracleReader].
#[derive(Debug, Clone, Copy)]
pub struct OracleServer {
pipe_handle: PipeHandle,
}

impl OracleServer {
/// Create a new [OracleServer] from a [PipeHandle].
pub fn new(pipe_handle: PipeHandle) -> Self {
Self { pipe_handle }
}
}

impl PreimageOracleServer for OracleServer {
fn next_preimage_request(
&self,
mut get_preimage: impl FnMut(PreimageKey) -> Result<Vec<u8>>,
) -> Result<()> {
// Read the preimage request from the client, and throw early if there isn't is any.
let mut buf = [0u8; 32];
self.pipe_handle.read_exact(&mut buf)?;
let preimage_key = PreimageKey::try_from(buf)?;

// Fetch the preimage value from the preimage getter.
let value = get_preimage(preimage_key)?;

// Write the length as a big-endian u64 followed by the data.
let data = [(value.len() as u64).to_be_bytes().as_ref(), value.as_ref()]
.into_iter()
.flatten()
.copied()
.collect::<Vec<_>>();
self.pipe_handle.write(data.as_slice())?;

Ok(())
}
}

#[cfg(test)]
mod test {
extern crate std;

use super::*;
use crate::PreimageKeyType;
use alloy_primitives::keccak256;
use kona_common::FileDescriptor;
use std::{fs::File, os::fd::AsRawFd};
use std::{collections::HashMap, fs::File, os::fd::AsRawFd};
use tempfile::tempfile;

/// Test struct containing the [OracleReader] and a [PipeHandle] for the host, plus the open
/// Test struct containing the [OracleReader] and a [OracleServer] for the host, plus the open
/// [File]s. The [File]s are stored in this struct so that they are not dropped until the
/// end of the test.
///
/// TODO: Swap host pipe handle to oracle writer once it exists.
#[derive(Debug)]
struct ClientAndHost {
oracle_reader: OracleReader,
host_handle: PipeHandle,
oracle_server: OracleServer,
_read_file: File,
_write_file: File,
}

/// Helper for creating a new [OracleReader] and [PipeHandle] for testing. The file channel is
/// Helper for creating a new [OracleReader] and [OracleServer] for testing. The file channel is
/// over two temporary files.
///
/// TODO: Swap host pipe handle to oracle writer once it exists.
fn client_and_host() -> ClientAndHost {
let (read_file, write_file) = (tempfile().unwrap(), tempfile().unwrap());
let (read_fd, write_fd) = (
Expand All @@ -96,27 +131,48 @@ mod test {
let host_handle = PipeHandle::new(write_fd, read_fd);

let oracle_reader = OracleReader::new(client_handle);
let oracle_server = OracleServer::new(host_handle);

ClientAndHost { oracle_reader, host_handle, _read_file: read_file, _write_file: write_file }
ClientAndHost {
oracle_reader,
oracle_server,
_read_file: read_file,
_write_file: write_file,
}
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_oracle_reader() {
const MOCK_DATA: &[u8] = b"1234567890";
async fn test_oracle_client_and_host() {
const MOCK_DATA_A: &[u8] = b"1234567890";
const MOCK_DATA_B: &[u8] = b"FACADE";
let key_a: PreimageKey =
PreimageKey::new(*keccak256(MOCK_DATA_A), PreimageKeyType::Keccak256);
let key_b: PreimageKey =
PreimageKey::new(*keccak256(MOCK_DATA_B), PreimageKeyType::Keccak256);

let mut preimages = HashMap::new();
preimages.insert(key_a, MOCK_DATA_A.to_vec());
preimages.insert(key_b, MOCK_DATA_B.to_vec());

let sys = client_and_host();
let (mut oracle_reader, host_handle) = (sys.oracle_reader, sys.host_handle);
let (oracle_reader, oracle_server) = (sys.oracle_reader, sys.oracle_server);

let client = tokio::task::spawn(async move {
oracle_reader.get(PreimageKey::new([0u8; 32], PreimageKeyType::Keccak256)).unwrap()
let contents_a = oracle_reader.get(key_a).unwrap();
let contents_b = oracle_reader.get(key_b).unwrap();
(contents_a, contents_b)
});
let host = tokio::task::spawn(async move {
let mut length_and_data: [u8; 8 + 10] = [0u8; 8 + 10];
length_and_data[0..8].copy_from_slice(&u64::to_be_bytes(MOCK_DATA.len() as u64));
length_and_data[8..18].copy_from_slice(MOCK_DATA);
host_handle.write(&length_and_data).unwrap();
let get_preimage =
|key| preimages.get(&key).ok_or(anyhow::anyhow!("Preimage not available")).cloned();

oracle_server.next_preimage_request(get_preimage).unwrap();
oracle_server.next_preimage_request(get_preimage).unwrap();
});

let (r, _) = tokio::join!(client, host);
assert_eq!(r.unwrap(), MOCK_DATA);
let (client, _) = tokio::join!(client, host);
let (contents_a, contents_b) = client.unwrap();
assert_eq!(contents_a, MOCK_DATA_A);
assert_eq!(contents_b, MOCK_DATA_B);
}
}
Loading

0 comments on commit 3d8cd85

Please sign in to comment.