Skip to content

Commit

Permalink
HintWriter (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby authored Feb 16, 2024
1 parent 36eb669 commit f4c6315
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 33 deletions.
62 changes: 62 additions & 0 deletions crates/preimage/src/hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::PipeHandle;
use alloc::vec;
use anyhow::{bail, Result};

/// A [HintWriter] is a high-level interface to the hint pipe. It provides a way to write hints to the host.
#[derive(Debug, Clone, Copy)]
pub struct HintWriter {
pipe_handle: PipeHandle,
}

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

/// Write a hint to the host. This will overwrite any existing hint in the pipe, and block until all data has been
/// written.
pub fn write(&self, hint: &str) -> Result<()> {
// Form the hint into a byte buffer. The format is a 4-byte big-endian length prefix followed by the hint
// string.
let mut hint_bytes = vec![0u8; hint.len() + 4];
hint_bytes[0..4].copy_from_slice(u32::to_be_bytes(hint.len() as u32).as_ref());
hint_bytes[4..].copy_from_slice(hint.as_bytes());

// Write the hint to the host.
let mut written = 0;
loop {
match self.pipe_handle.write(&hint_bytes[written..]) {
Ok(0) => break,
Ok(n) => {
written += n as usize;
continue;
}
Err(e) => bail!("Failed to write preimage key: {}", e),
}
}

// Read the hint acknowledgement from the host.
let mut hint_ack = [0u8; 1];
self.read_exact(&mut hint_ack)?;

Ok(())
}

/// Reads bytes into `buf` and returns the number of bytes read.
fn read(&self, buf: &mut [u8]) -> Result<usize> {
let read = self.pipe_handle.read(buf)?;
Ok(read as usize)
}

/// Reads exactly `buf.len()` bytes into `buf`, blocking until all bytes are read.
fn read_exact(&self, buf: &mut [u8]) -> Result<()> {
let mut read = 0;
while read < buf.len() {
let chunk_read = self.read(&mut buf[read..])?;
read += chunk_read;
}

Ok(())
}
}
Empty file removed crates/preimage/src/hint_writer.rs
Empty file.
3 changes: 3 additions & 0 deletions crates/preimage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ pub use key::{PreimageKey, PreimageKeyType};
mod oracle;
pub use oracle::OracleReader;

mod hint;
pub use hint::HintWriter;

mod pipe;
pub use pipe::PipeHandle;
29 changes: 21 additions & 8 deletions examples/simple-revm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use alloc::vec::Vec;
use anyhow::{anyhow, bail, Result};
use kona_common::{io, FileDescriptor};
use kona_preimage::{OracleReader, PipeHandle, PreimageKey, PreimageKeyType};
use kona_preimage::{HintWriter, OracleReader, PipeHandle, PreimageKey, PreimageKeyType};
use revm::{
db::{CacheDB, EmptyDB},
primitives::{
Expand All @@ -27,15 +27,20 @@ const CODE_KEY: B256 = b256!("00000000000000000000000000000000000000000000000000

static CLIENT_PREIMAGE_PIPE: PipeHandle =
PipeHandle::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite);
static CLIENT_HINT_PIPE: PipeHandle =
PipeHandle::new(FileDescriptor::HintRead, FileDescriptor::HintWrite);

#[no_mangle]
pub extern "C" fn _start() {
kona_common::alloc_heap!(HEAP_SIZE);

let mut oracle = OracleReader::new(CLIENT_PREIMAGE_PIPE);
let hint_writer = HintWriter::new(CLIENT_HINT_PIPE);

io::print("Booting EVM and checking hash...\n");
let (input, digest, code) = boot().expect("Failed to boot");
let (digest, code) = boot(&mut oracle).expect("Failed to boot");

match run_evm(input, digest, code) {
match run_evm(&mut oracle, &hint_writer, digest, code) {
Ok(_) => io::print("Success, hashes matched!\n"),
Err(e) => {
let _ = io::print_err(alloc::format!("Error: {}\n", e).as_ref());
Expand All @@ -47,20 +52,28 @@ pub extern "C" fn _start() {
}

/// Boot the program and load bootstrap information.
fn boot() -> Result<(Vec<u8>, [u8; 32], Vec<u8>)> {
let mut oracle = OracleReader::new(CLIENT_PREIMAGE_PIPE);
let input = oracle.get(PreimageKey::new(*INPUT_KEY, PreimageKeyType::Local))?;
fn boot(oracle: &mut OracleReader) -> Result<([u8; 32], Vec<u8>)> {
let digest = oracle
.get(PreimageKey::new(*DIGEST_KEY, PreimageKeyType::Local))?
.try_into()
.map_err(|_| anyhow!("Failed to convert digest to [u8; 32]"))?;
let code = oracle.get(PreimageKey::new(*CODE_KEY, PreimageKeyType::Local))?;

Ok((input, digest, code))
Ok((digest, code))
}

/// Call the SHA-256 precompile and assert that the input and output match the expected values
fn run_evm(input: Vec<u8>, digest: [u8; 32], code: Vec<u8>) -> Result<()> {
fn run_evm(
oracle: &mut OracleReader,
hint_writer: &HintWriter,
digest: [u8; 32],
code: Vec<u8>,
) -> Result<()> {
// Send a hint for the preimage of the digest to the host so that it can prepare the preimage.
hint_writer.write(&alloc::format!("sha2-preimage {}", hex::encode(digest)))?;
// Get the preimage of `digest` from the host.
let input = oracle.get(PreimageKey::new(*INPUT_KEY, PreimageKeyType::Local))?;

let mut cache_db = CacheDB::new(EmptyDB::default());

// Insert EVM identity contract into database.
Expand Down
18 changes: 15 additions & 3 deletions fpvm-tests/asterisc-tests/simple_revm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package asterisc_test
import (
"crypto/sha256"
"debug/elf"
"strings"
"testing"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -14,11 +15,13 @@ import (

func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr string) {
images := make(map[[32]byte][]byte)
sha2Preimages := make(map[[32]byte][]byte)

input := []byte("facade facade facade")
shaHash := sha256.Sum256(input)
// shaHash[0] = 0x01
images[preimage.LocalIndexKey(0).PreimageKey()] = input
images[preimage.LocalIndexKey(1).PreimageKey()] = shaHash[:]
sha2Preimages[shaHash] = input

// CALLDATASIZE
// PUSH0
// PUSH0
Expand All @@ -30,7 +33,16 @@ func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr stri

oracle := &testOracle{
hint: func(v []byte) {
// no-op
hintStr := string(v)
hintParts := strings.Split(hintStr, " ")

switch hintParts[0] {
case "sha2-preimage":
hash := common.HexToHash(hintParts[1])
images[preimage.LocalIndexKey(0).PreimageKey()] = sha2Preimages[hash]
default:
t.Fatalf("unknown hint: %s", hintStr)
}
},
getPreimage: func(k [32]byte) []byte {
p, ok := images[k]
Expand Down
8 changes: 3 additions & 5 deletions fpvm-tests/cannon-go-tests/minimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ func TestMinimal(t *testing.T) {
us := mipsevm.NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))

for i := 0; i < 200_000; i++ {
wit, err := us.Step(true)
_, err := us.Step(true)
require.NoError(t, err)
// hack: state isn't exposed in `InstrumentedState`, so we generate the
// state witness each step and check for the exit condition
if wit != nil && wit.State[89] == 1 {
fmt.Printf("exited @ step #%d\n", i)
if state.Exited {
fmt.Printf("exited @ step #%d\n", state.Step)
break
}
}
Expand Down
26 changes: 18 additions & 8 deletions fpvm-tests/cannon-go-tests/simple_revm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"strings"
"testing"

"github.com/ethereum-optimism/optimism/cannon/mipsevm"
Expand Down Expand Up @@ -38,11 +39,13 @@ var _ PreimageOracle = (*testOracle)(nil)

func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr string) {
images := make(map[[32]byte][]byte)
sha2Preimages := make(map[[32]byte][]byte)

input := []byte("facade facade facade")
shaHash := sha256.Sum256(input)
// shaHash[0] = 0x01
images[preimage.LocalIndexKey(0).PreimageKey()] = input
images[preimage.LocalIndexKey(1).PreimageKey()] = shaHash[:]
sha2Preimages[shaHash] = input

// CALLDATASIZE
// PUSH0
// PUSH0
Expand All @@ -54,7 +57,16 @@ func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr stri

oracle := &testOracle{
hint: func(v []byte) {
// no-op
hintStr := string(v)
hintParts := strings.Split(hintStr, " ")

switch hintParts[0] {
case "sha2-preimage":
hash := common.HexToHash(hintParts[1])
images[preimage.LocalIndexKey(0).PreimageKey()] = sha2Preimages[hash]
default:
t.Fatalf("unknown hint: %s", hintStr)
}
},
getPreimage: func(k [32]byte) []byte {
p, ok := images[k]
Expand All @@ -81,12 +93,10 @@ func TestSimpleRevm(t *testing.T) {
us := mipsevm.NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))

for i := 0; i < 200_000; i++ {
wit, err := us.Step(true)
_, err := us.Step(false)
require.NoError(t, err)
// hack: state isn't exposed in `InstrumentedState`, so we generate the
// state witness each step and check for the exit condition
if wit != nil && wit.State[89] == 1 {
fmt.Printf("exited @ step #%d\n", i)
if state.Exited {
fmt.Printf("exited @ step #%d\n", state.Step)
break
}
}
Expand Down
38 changes: 29 additions & 9 deletions fpvm-tests/cannon-rs-tests/src/simple_revm_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

use alloy_primitives::hex;
use anyhow::Result;
use cannon_mipsevm::{
load_elf, patch_stack, InstrumentedState, PreimageOracle,
};
use cannon_mipsevm::{load_elf, patch_stack, InstrumentedState, PreimageOracle};
use preimage_oracle::{Hint, Key, LocalIndexKey};
use sha2::{Digest, Sha256};
use std::{collections::HashMap, io::BufWriter};
Expand Down Expand Up @@ -40,6 +38,7 @@ fn test_simple_revm() {

pub struct RevmTestOracle {
images: HashMap<[u8; 32], Vec<u8>>,
sha2_preimages: HashMap<[u8; 32], Vec<u8>>,
}

impl RevmTestOracle {
Expand All @@ -50,25 +49,46 @@ impl RevmTestOracle {
let input_hash = hasher.finalize();

let mut images = HashMap::new();
images.insert((0 as LocalIndexKey).preimage_key(), INPUT.to_vec());
images.insert((1 as LocalIndexKey).preimage_key(), input_hash.to_vec());
images.insert(
(2 as LocalIndexKey).preimage_key(),
hex!("365f5f37365ff3").to_vec(),
);

Self { images }
let mut sha2_preimages = HashMap::new();
sha2_preimages.insert(input_hash.try_into().unwrap(), INPUT.to_vec());

Self {
images,
sha2_preimages,
}
}
}

impl PreimageOracle for RevmTestOracle {
fn hint(&mut self, _: impl Hint) -> Result<()> {
// no-op
Ok(())
fn hint(&mut self, hint: impl Hint) -> Result<()> {
let hint_str = std::str::from_utf8(hint.hint())?;
let hint_parts = hint_str.split_whitespace().collect::<Vec<_>>();

match hint_parts[0] {
"sha2-preimage" => {
let hash: [u8; 32] = hex::decode(hint_parts[1])?
.try_into()
.map_err(|_| anyhow::anyhow!("Failed to parse hash"))?;
self.images.insert(
(0 as LocalIndexKey).preimage_key(),
self.sha2_preimages
.get(&hash)
.ok_or(anyhow::anyhow!("No preimage for hash"))?
.to_vec(),
);
Ok(())
}
_ => anyhow::bail!("Unknown hint: {}", hint_str),
}
}

fn get(&mut self, key: [u8; 32]) -> Result<Vec<u8>> {
dbg!(&key);
Ok(self
.images
.get(&key)
Expand Down

0 comments on commit f4c6315

Please sign in to comment.