Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HintWriter #23

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
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
Loading