Skip to content

Commit 54a9959

Browse files
authored
Merge pull request #4472 from OffchainLabs/pmikolajczyk/nit-4614-port-wavmio
Move wavmio logic to caller-env
2 parents 35986d4 + f453fd3 commit 54a9959

File tree

8 files changed

+250
-108
lines changed

8 files changed

+250
-108
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ enum-iterator = { version = "2.0.1" }
5555
eyre = { version = "0.6.5" }
5656
fnv = { version = "1.0.7" }
5757
gperftools = { version = "0.2.0" }
58-
hex = { version = "0.4.3" }
58+
hex = { version = "0.4.3", default-features = false }
5959
k256 = { version = "0.13.4", default-features = false}
6060
lazy_static = { version = "1.4.0" }
6161
libc = { version = "0.2.132" }

changelog/pmikolajczyk-nit-4614.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Internal
2+
- Move wavmio logic from JIT crate to caller-env (to be reused soon by SP1 validator)

crates/caller-env/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ rust-version.workspace = true
1111

1212
[dependencies]
1313
brotli = { workspace = true, optional = true }
14+
hex = { workspace = true, features = ["alloc"] }
1415
k256 = { workspace = true, features = ["ecdsa"] }
1516
rand = { workspace = true }
1617
rand_pcg = { workspace = true }

crates/caller-env/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod wasmer_traits;
2121
pub mod brotli;
2222

2323
pub mod arbcrypto;
24+
pub mod wavmio;
2425

2526
mod guest_ptr;
2627
pub mod wasip1_stub;

crates/caller-env/src/wavmio.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright 2026, Offchain Labs, Inc.
2+
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md
3+
4+
use crate::{GuestPtr, MemAccess};
5+
use alloc::format;
6+
use alloc::string::String;
7+
use core::cmp::min;
8+
9+
/// Read validation inputs and set outputs for the `wavmio` host functions.
10+
pub trait WavmIo {
11+
fn get_u64_global(&self, idx: usize) -> Option<u64>;
12+
fn set_u64_global(&mut self, idx: usize, val: u64) -> bool;
13+
fn get_bytes32_global(&self, idx: usize) -> Option<&[u8; 32]>;
14+
fn set_bytes32_global(&mut self, idx: usize, val: [u8; 32]) -> bool;
15+
fn get_sequencer_message(&self, num: u64) -> Option<&[u8]>;
16+
fn get_delayed_message(&self, num: u64) -> Option<&[u8]>;
17+
fn get_preimage(&self, preimage_type: u8, hash: &[u8; 32]) -> Option<&[u8]>;
18+
}
19+
20+
/// Reads 32-bytes of global state and writes to guest memory.
21+
pub fn get_global_state_bytes32(
22+
mem: &mut impl MemAccess,
23+
io: &impl WavmIo,
24+
idx: u32,
25+
out_ptr: GuestPtr,
26+
) -> Result<(), String> {
27+
let Some(global) = io.get_bytes32_global(idx as usize) else {
28+
return Err("global read out of bounds in wavmio.getGlobalStateBytes32".into());
29+
};
30+
mem.write_slice(out_ptr, &global[..]);
31+
Ok(())
32+
}
33+
34+
/// Reads 32-bytes from guest memory and writes to global state.
35+
pub fn set_global_state_bytes32(
36+
mem: &impl MemAccess,
37+
io: &mut impl WavmIo,
38+
idx: u32,
39+
src_ptr: GuestPtr,
40+
) -> Result<(), String> {
41+
let val = mem.read_fixed(src_ptr);
42+
if !io.set_bytes32_global(idx as usize, val) {
43+
return Err("global write oob in wavmio.setGlobalStateBytes32".into());
44+
}
45+
Ok(())
46+
}
47+
48+
/// Reads 8-bytes of global state.
49+
pub fn get_global_state_u64(io: &impl WavmIo, idx: u32) -> Result<u64, String> {
50+
match io.get_u64_global(idx as usize) {
51+
Some(val) => Ok(val),
52+
None => Err("global read out of bounds in wavmio.getGlobalStateU64".into()),
53+
}
54+
}
55+
56+
/// Writes 8-bytes of global state.
57+
pub fn set_global_state_u64(io: &mut impl WavmIo, idx: u32, val: u64) -> Result<(), String> {
58+
if !io.set_u64_global(idx as usize, val) {
59+
return Err("global write out of bounds in wavmio.setGlobalStateU64".into());
60+
}
61+
Ok(())
62+
}
63+
64+
/// Reads up to 32 bytes of a sequencer inbox message at the given offset.
65+
pub fn read_inbox_message(
66+
mem: &mut impl MemAccess,
67+
io: &impl WavmIo,
68+
msg_num: u64,
69+
offset: u32,
70+
out_ptr: GuestPtr,
71+
) -> Result<u32, String> {
72+
let message = io
73+
.get_sequencer_message(msg_num)
74+
.ok_or(format!("missing sequencer inbox message {msg_num}"))?;
75+
read_message(mem, message, offset, out_ptr)
76+
}
77+
78+
/// Reads up to 32 bytes of a delayed inbox message at the given offset.
79+
pub fn read_delayed_inbox_message(
80+
mem: &mut impl MemAccess,
81+
io: &impl WavmIo,
82+
msg_num: u64,
83+
offset: u32,
84+
out_ptr: GuestPtr,
85+
) -> Result<u32, String> {
86+
let message = io
87+
.get_delayed_message(msg_num)
88+
.ok_or(format!("missing delayed inbox message {msg_num}"))?;
89+
read_message(mem, message, offset, out_ptr)
90+
}
91+
92+
fn read_message(
93+
mem: &mut impl MemAccess,
94+
message: &[u8],
95+
offset: u32,
96+
out_ptr: GuestPtr,
97+
) -> Result<u32, String> {
98+
let offset = offset as usize;
99+
let len = min(32, message.len().saturating_sub(offset));
100+
let read = message.get(offset..(offset + len)).unwrap_or_default();
101+
mem.write_slice(out_ptr, read);
102+
Ok(read.len() as u32)
103+
}
104+
105+
/// Looks up a preimage by type and hash, reads up to 32 bytes at an aligned offset.
106+
pub fn resolve_preimage(
107+
mem: &mut impl MemAccess,
108+
io: &impl WavmIo,
109+
preimage_type: u8,
110+
hash_ptr: GuestPtr,
111+
offset: u32,
112+
out_ptr: GuestPtr,
113+
name: &str,
114+
) -> Result<u32, String> {
115+
let hash = mem.read_fixed(hash_ptr);
116+
let offset = offset as usize;
117+
118+
let Some(preimage) = io.get_preimage(preimage_type, &hash) else {
119+
let hash_hex = hex::encode(hash);
120+
return Err(format!(
121+
"Missing requested preimage for hash {hash_hex} in {name}"
122+
));
123+
};
124+
125+
if offset % 32 != 0 {
126+
return Err(format!("bad offset {offset} in {name}"));
127+
}
128+
129+
let len = min(32, preimage.len().saturating_sub(offset));
130+
let read = preimage.get(offset..(offset + len)).unwrap_or_default();
131+
mem.write_slice(out_ptr, read);
132+
Ok(read.len() as u32)
133+
}
134+
135+
/// Returns 1 if a preimage exists for the given type and hash, 0 otherwise.
136+
pub fn validate_certificate(
137+
mem: &impl MemAccess,
138+
io: &impl WavmIo,
139+
preimage_type: u8,
140+
hash_ptr: GuestPtr,
141+
) -> u8 {
142+
let hash = mem.read_fixed(hash_ptr);
143+
match io.get_preimage(preimage_type, &hash) {
144+
Some(_) => 1,
145+
None => 0,
146+
}
147+
}

crates/jit/src/caller_env.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use crate::machine::{WasmEnv, WasmEnvMut};
55
use arbutil::{Bytes20, Bytes32};
6-
use caller_env::{ExecEnv, GuestPtr, MemAccess};
6+
use caller_env::{wavmio::WavmIo, ExecEnv, GuestPtr, MemAccess};
77
use rand::RngCore;
88
use std::mem::{self, MaybeUninit};
99
use wasmer::{Memory, MemoryView, StoreMut, WasmPtr};
@@ -132,3 +132,48 @@ impl ExecEnv for JitExecEnv<'_> {
132132
}
133133
}
134134
}
135+
136+
impl WavmIo for WasmEnv {
137+
fn get_u64_global(&self, idx: usize) -> Option<u64> {
138+
self.small_globals.get(idx).copied()
139+
}
140+
141+
fn set_u64_global(&mut self, idx: usize, val: u64) -> bool {
142+
let Some(g) = self.small_globals.get_mut(idx) else {
143+
return false;
144+
};
145+
*g = val;
146+
true
147+
}
148+
149+
fn get_bytes32_global(&self, idx: usize) -> Option<&[u8; 32]> {
150+
self.large_globals.get(idx).map(|b| &b.0)
151+
}
152+
153+
fn set_bytes32_global(&mut self, idx: usize, val: [u8; 32]) -> bool {
154+
let Some(g) = self.large_globals.get_mut(idx) else {
155+
return false;
156+
};
157+
*g = val.into();
158+
true
159+
}
160+
161+
fn get_sequencer_message(&self, num: u64) -> Option<&[u8]> {
162+
self.sequencer_messages.get(&num).map(|v| v.as_slice())
163+
}
164+
165+
fn get_delayed_message(&self, num: u64) -> Option<&[u8]> {
166+
self.delayed_messages.get(&num).map(|v| v.as_slice())
167+
}
168+
169+
fn get_preimage(&self, preimage_type: u8, hash: &[u8; 32]) -> Option<&[u8]> {
170+
let Ok(pt) = preimage_type.try_into() else {
171+
eprintln!("Go trying to get a preimage with unknown type {preimage_type}");
172+
return None;
173+
};
174+
self.preimages
175+
.get(&pt)
176+
.and_then(|m| m.get(&Bytes32(*hash)))
177+
.map(|v| v.as_slice())
178+
}
179+
}

0 commit comments

Comments
 (0)