Skip to content

Commit c56d778

Browse files
authored
feat(wasm-runtime-interface): align ABI, register storage host functions, add log/timestamp support (#32)
Fix critical ABI mismatches in network host functions and implement missing storage host function registrations to align the host-side runtime with the guest SDK expectations. Network ABI fixes (network.rs): - Remove extra resp_len param from http_get and dns_resolve signatures to match the guest SDK 4-param ABI (req_ptr, req_len, resp_ptr) with default buffer sizes (64KB for HTTP, 4KB for DNS). - Add extra _extra param to http_post to align with guest expectations. - Add log_message host function: reads message string from WASM memory and dispatches to tracing::info/warn/error based on level parameter. - Add get_timestamp host function: returns UTC millis, with support for a fixed_timestamp_ms in RuntimeState for deterministic consensus. Storage host functions (storage.rs): - Implement StorageHostFunctions struct with HostFunctionRegistrar trait, registering storage_get and storage_set under platform_storage module. - storage_get reads key from WASM memory, fetches from backend, writes value back to WASM memory, tracks bytes_read and operations_count. - storage_set reads key+value from WASM memory, validates against config constraints (key/value limits, consensus gating), writes via backend, tracks bytes_written and operations_count. - Add helper functions for WASM memory read/write with bounds checking. - Add StorageOperation::Set variant and HOST_STORAGE_SET constant. - Update StorageHostState::new to accept a StorageBackend Arc parameter. Runtime integration (runtime.rs): - Extend InstanceConfig and RuntimeState with storage_state, storage_backend, storage_host_config, and fixed_timestamp_ms fields. - Register both NetworkHostFunctions and StorageHostFunctions with the linker during instance creation. - Add storage state reset and counter accessors to ChallengeInstance. Module exports (lib.rs): - Export StorageHostFunctions, HOST_STORAGE_SET, HOST_LOG_MESSAGE, and HOST_GET_TIMESTAMP from the crate root. Downstream update (wasm_executor.rs): - Use ..Default::default() in InstanceConfig construction to pick up new fields without breaking existing call sites.
1 parent fc5783c commit c56d778

File tree

5 files changed

+341
-26
lines changed

5 files changed

+341
-26
lines changed

bins/validator-node/src/wasm_executor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ impl WasmChallengeExecutor {
9191
validator_id: "validator".to_string(),
9292
restart_id: String::new(),
9393
config_version: 0,
94+
..Default::default()
9495
};
9596

9697
let mut instance = self
@@ -181,6 +182,7 @@ impl WasmChallengeExecutor {
181182
validator_id: "validator".to_string(),
182183
restart_id: String::new(),
183184
config_version: 0,
185+
..Default::default()
184186
};
185187

186188
let mut instance = self

crates/wasm-runtime-interface/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ pub use exec::{
2323
ExecError, ExecHostFunction, ExecHostFunctions, ExecPolicy, ExecRequest, ExecResponse,
2424
ExecState,
2525
};
26-
pub use network::{NetworkHostFunctions, NetworkState, NetworkStateError};
26+
pub use network::{
27+
NetworkHostFunctions, NetworkState, NetworkStateError, HOST_GET_TIMESTAMP, HOST_LOG_MESSAGE,
28+
};
2729
pub use storage::{
2830
InMemoryStorageBackend, NoopStorageBackend, StorageAuditEntry, StorageAuditLogger,
2931
StorageBackend, StorageDeleteRequest, StorageGetRequest, StorageGetResponse, StorageHostConfig,
30-
StorageHostError, StorageHostState, StorageHostStatus, StorageOperation,
32+
StorageHostError, StorageHostFunctions, StorageHostState, StorageHostStatus, StorageOperation,
3133
StorageProposeWriteRequest, StorageProposeWriteResponse,
3234
};
3335

@@ -43,7 +45,7 @@ pub use runtime::{
4345
};
4446
pub use storage::{
4547
HOST_STORAGE_ALLOC, HOST_STORAGE_DELETE, HOST_STORAGE_GET, HOST_STORAGE_GET_RESULT,
46-
HOST_STORAGE_NAMESPACE, HOST_STORAGE_PROPOSE_WRITE,
48+
HOST_STORAGE_NAMESPACE, HOST_STORAGE_PROPOSE_WRITE, HOST_STORAGE_SET,
4749
};
4850
pub use time::{TimeError, TimeHostFunction, TimeHostFunctions, TimeMode, TimePolicy, TimeState};
4951

crates/wasm-runtime-interface/src/network.rs

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ use std::io::Read;
1313
use std::net::IpAddr;
1414
use std::sync::Arc;
1515
use std::time::{Duration, Instant};
16-
use tracing::{info, warn};
16+
use tracing::{error, info, warn};
1717
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
1818
use trust_dns_resolver::proto::rr::RecordType;
1919
use trust_dns_resolver::Resolver;
2020
use wasmtime::{Caller, Linker, Memory};
2121

2222
use crate::runtime::{HostFunctionRegistrar, RuntimeState, WasmRuntimeError};
2323

24+
pub const HOST_LOG_MESSAGE: &str = "log_message";
25+
pub const HOST_GET_TIMESTAMP: &str = "get_timestamp";
26+
27+
const DEFAULT_RESPONSE_BUF_SIZE: i32 = 65536;
28+
const DEFAULT_DNS_BUF_SIZE: i32 = 4096;
29+
2430
#[derive(Debug, thiserror::Error)]
2531
pub enum NetworkStateError {
2632
#[error("network policy invalid: {0}")]
@@ -86,10 +92,9 @@ impl HostFunctionRegistrar for NetworkHostFunctions {
8692
|mut caller: Caller<RuntimeState>,
8793
req_ptr: i32,
8894
req_len: i32,
89-
resp_ptr: i32,
90-
resp_len: i32|
95+
resp_ptr: i32|
9196
-> i32 {
92-
handle_http_get(&mut caller, req_ptr, req_len, resp_ptr, resp_len)
97+
handle_http_get(&mut caller, req_ptr, req_len, resp_ptr)
9398
},
9499
)
95100
.map_err(|err| WasmRuntimeError::HostFunction(err.to_string()))?;
@@ -104,7 +109,8 @@ impl HostFunctionRegistrar for NetworkHostFunctions {
104109
req_ptr: i32,
105110
req_len: i32,
106111
resp_ptr: i32,
107-
resp_len: i32|
112+
resp_len: i32,
113+
_extra: i32|
108114
-> i32 {
109115
handle_http_post(&mut caller, req_ptr, req_len, resp_ptr, resp_len)
110116
},
@@ -120,15 +126,32 @@ impl HostFunctionRegistrar for NetworkHostFunctions {
120126
|mut caller: Caller<RuntimeState>,
121127
req_ptr: i32,
122128
req_len: i32,
123-
resp_ptr: i32,
124-
resp_len: i32|
129+
resp_ptr: i32|
125130
-> i32 {
126-
handle_dns_request(&mut caller, req_ptr, req_len, resp_ptr, resp_len)
131+
handle_dns_request(&mut caller, req_ptr, req_len, resp_ptr)
127132
},
128133
)
129134
.map_err(|err| WasmRuntimeError::HostFunction(err.to_string()))?;
130135
}
131136

137+
linker
138+
.func_wrap(
139+
HOST_FUNCTION_NAMESPACE,
140+
HOST_LOG_MESSAGE,
141+
|mut caller: Caller<RuntimeState>, level: i32, msg_ptr: i32, msg_len: i32| {
142+
handle_log_message(&mut caller, level, msg_ptr, msg_len);
143+
},
144+
)
145+
.map_err(|err| WasmRuntimeError::HostFunction(err.to_string()))?;
146+
147+
linker
148+
.func_wrap(
149+
HOST_FUNCTION_NAMESPACE,
150+
HOST_GET_TIMESTAMP,
151+
|caller: Caller<RuntimeState>| -> i64 { handle_get_timestamp(&caller) },
152+
)
153+
.map_err(|err| WasmRuntimeError::HostFunction(err.to_string()))?;
154+
132155
Ok(())
133156
}
134157
}
@@ -147,11 +170,6 @@ pub struct NetworkState {
147170
}
148171

149172
impl NetworkState {
150-
/// Create network state for a single WASM execution.
151-
///
152-
/// The policy is validated and then enforced by every host call. All host
153-
/// functions share the same counters and audit logger, providing a single
154-
/// enforcement surface for HTTP and DNS access.
155173
pub fn new(
156174
policy: NetworkPolicy,
157175
audit_logger: Option<Arc<dyn NetworkAuditLogger>>,
@@ -578,8 +596,8 @@ fn handle_http_get(
578596
req_ptr: i32,
579597
req_len: i32,
580598
resp_ptr: i32,
581-
resp_len: i32,
582599
) -> i32 {
600+
let resp_len = DEFAULT_RESPONSE_BUF_SIZE;
583601
let enforcement = "http_get";
584602
let request_bytes = match read_memory(caller, req_ptr, req_len) {
585603
Ok(bytes) => bytes,
@@ -678,8 +696,8 @@ fn handle_dns_request(
678696
req_ptr: i32,
679697
req_len: i32,
680698
resp_ptr: i32,
681-
resp_len: i32,
682699
) -> i32 {
700+
let resp_len = DEFAULT_DNS_BUF_SIZE;
683701
let enforcement = "dns_resolve";
684702
let request_bytes = match read_memory(caller, req_ptr, req_len) {
685703
Ok(bytes) => bytes,
@@ -716,6 +734,34 @@ fn handle_dns_request(
716734
write_result(caller, resp_ptr, resp_len, result)
717735
}
718736

737+
fn handle_log_message(caller: &mut Caller<RuntimeState>, level: i32, msg_ptr: i32, msg_len: i32) {
738+
let msg = match read_memory(caller, msg_ptr, msg_len) {
739+
Ok(bytes) => String::from_utf8_lossy(&bytes).into_owned(),
740+
Err(err) => {
741+
warn!(
742+
challenge_id = %caller.data().challenge_id,
743+
error = %err,
744+
"log_message: failed to read message from wasm memory"
745+
);
746+
return;
747+
}
748+
};
749+
750+
let challenge_id = caller.data().challenge_id.clone();
751+
match level {
752+
0 => info!(challenge_id = %challenge_id, "[wasm] {}", msg),
753+
1 => warn!(challenge_id = %challenge_id, "[wasm] {}", msg),
754+
_ => error!(challenge_id = %challenge_id, "[wasm] {}", msg),
755+
}
756+
}
757+
758+
fn handle_get_timestamp(caller: &Caller<RuntimeState>) -> i64 {
759+
if let Some(ts) = caller.data().fixed_timestamp_ms {
760+
return ts;
761+
}
762+
chrono::Utc::now().timestamp_millis()
763+
}
764+
719765
fn resolve_dns(
720766
resolver: &Resolver,
721767
request: &DnsRequest,

crates/wasm-runtime-interface/src/runtime.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use crate::bridge::{self, BridgeError, EvalRequest, EvalResponse};
22
use crate::exec::{ExecPolicy, ExecState};
3+
use crate::storage::{
4+
InMemoryStorageBackend, StorageBackend, StorageHostConfig, StorageHostFunctions,
5+
StorageHostState,
6+
};
37
use crate::time::{TimePolicy, TimeState};
4-
use crate::{NetworkAuditLogger, NetworkPolicy, NetworkState};
8+
use crate::{NetworkAuditLogger, NetworkHostFunctions, NetworkPolicy, NetworkState};
59
use std::sync::Arc;
610
use std::time::Instant;
711
use thiserror::Error;
@@ -103,6 +107,12 @@ pub struct InstanceConfig {
103107
pub restart_id: String,
104108
/// Configuration version for hot-restarts.
105109
pub config_version: u64,
110+
/// Storage host function configuration.
111+
pub storage_host_config: StorageHostConfig,
112+
/// Storage backend implementation.
113+
pub storage_backend: Arc<dyn StorageBackend>,
114+
/// Fixed timestamp for deterministic consensus execution.
115+
pub fixed_timestamp_ms: Option<i64>,
106116
}
107117

108118
impl Default for InstanceConfig {
@@ -117,6 +127,9 @@ impl Default for InstanceConfig {
117127
validator_id: "unknown".to_string(),
118128
restart_id: String::new(),
119129
config_version: 0,
130+
storage_host_config: StorageHostConfig::default(),
131+
storage_backend: Arc::new(InMemoryStorageBackend::new()),
132+
fixed_timestamp_ms: None,
120133
}
121134
}
122135
}
@@ -140,6 +153,10 @@ pub struct RuntimeState {
140153
pub restart_id: String,
141154
/// Configuration version for hot-restarts.
142155
pub config_version: u64,
156+
/// Storage host state for key-value operations.
157+
pub storage_state: StorageHostState,
158+
/// Fixed timestamp in milliseconds for deterministic consensus execution.
159+
pub fixed_timestamp_ms: Option<i64>,
143160
limits: StoreLimits,
144161
}
145162

@@ -155,6 +172,8 @@ impl RuntimeState {
155172
validator_id: String,
156173
restart_id: String,
157174
config_version: u64,
175+
storage_state: StorageHostState,
176+
fixed_timestamp_ms: Option<i64>,
158177
limits: StoreLimits,
159178
) -> Self {
160179
Self {
@@ -167,6 +186,8 @@ impl RuntimeState {
167186
validator_id,
168187
restart_id,
169188
config_version,
189+
storage_state,
190+
fixed_timestamp_ms,
170191
limits,
171192
}
172193
}
@@ -175,6 +196,10 @@ impl RuntimeState {
175196
self.network_state.reset_counters();
176197
}
177198

199+
pub fn reset_storage_counters(&mut self) {
200+
self.storage_state.reset_counters();
201+
}
202+
178203
pub fn reset_exec_counters(&mut self) {
179204
self.exec_state.reset_counters();
180205
}
@@ -242,6 +267,11 @@ impl WasmRuntime {
242267
instance_config.validator_id.clone(),
243268
)
244269
.map_err(|err| WasmRuntimeError::HostFunction(err.to_string()))?;
270+
let storage_state = StorageHostState::new(
271+
instance_config.challenge_id.clone(),
272+
instance_config.storage_host_config.clone(),
273+
Arc::clone(&instance_config.storage_backend),
274+
);
245275
let exec_state = ExecState::new(
246276
instance_config.exec_policy.clone(),
247277
instance_config.challenge_id.clone(),
@@ -262,6 +292,8 @@ impl WasmRuntime {
262292
instance_config.validator_id.clone(),
263293
instance_config.restart_id.clone(),
264294
instance_config.config_version,
295+
storage_state,
296+
instance_config.fixed_timestamp_ms,
265297
limits.build(),
266298
);
267299
let mut store = Store::new(&self.engine, runtime_state);
@@ -277,6 +309,13 @@ impl WasmRuntime {
277309
store.limiter(|state| &mut state.limits);
278310

279311
let mut linker = Linker::new(&self.engine);
312+
313+
let network_host_fns = NetworkHostFunctions::all();
314+
network_host_fns.register(&mut linker)?;
315+
316+
let storage_host_fns = StorageHostFunctions::new();
317+
storage_host_fns.register(&mut linker)?;
318+
280319
if let Some(registrar) = registrar {
281320
registrar.register(&mut linker)?;
282321
}
@@ -436,6 +475,22 @@ impl ChallengeInstance {
436475
self.store.data_mut().reset_network_counters();
437476
}
438477

478+
pub fn reset_storage_state(&mut self) {
479+
self.store.data_mut().reset_storage_counters();
480+
}
481+
482+
pub fn storage_bytes_read(&self) -> u64 {
483+
self.store.data().storage_state.bytes_read
484+
}
485+
486+
pub fn storage_bytes_written(&self) -> u64 {
487+
self.store.data().storage_state.bytes_written
488+
}
489+
490+
pub fn storage_operations_count(&self) -> u32 {
491+
self.store.data().storage_state.operations_count
492+
}
493+
439494
pub fn challenge_id(&self) -> &str {
440495
&self.store.data().challenge_id
441496
}

0 commit comments

Comments
 (0)