Skip to content
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
18 changes: 14 additions & 4 deletions bins/validator-node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ async fn main() -> Result<()> {
max_memory_bytes: args.wasm_max_memory,
enable_fuel: args.wasm_enable_fuel,
fuel_limit: args.wasm_fuel_limit,
storage_host_config: wasm_runtime_interface::StorageHostConfig::default(),
}) {
Ok(executor) => {
info!(
Expand Down Expand Up @@ -985,28 +986,37 @@ async fn process_wasm_evaluations(
let network_policy = wasm_runtime_interface::NetworkPolicy::default();

let input_data = submission_id.as_bytes().to_vec();
let challenge_id_str = challenge_id.to_string();

let executor = Arc::clone(executor);
let module_filename_clone = module_filename.clone();

let result = tokio::task::spawn_blocking(move || {
executor.execute_evaluation(&module_filename_clone, &network_policy, &input_data)
executor.execute_evaluation(
&module_filename_clone,
&network_policy,
&input_data,
&challenge_id_str,
&[],
)
})
.await;

let (score, eval_metrics) = match result {
Ok(Ok((score, metrics))) => {
Ok(Ok((output, metrics))) => {
info!(
submission_id = %submission_id,
challenge_id = %challenge_id,
score,
score = output.score,
valid = output.valid,
message = %output.message,
execution_time_ms = metrics.execution_time_ms,
memory_bytes = metrics.memory_used_bytes,
network_requests = metrics.network_requests_made,
fuel_consumed = ?metrics.fuel_consumed,
"WASM evaluation succeeded"
);
let normalized = (score as f64) / i64::MAX as f64;
let normalized = (output.score as f64) / i64::MAX as f64;
let em = EvaluationMetrics {
primary_score: normalized,
secondary_metrics: vec![],
Expand Down
135 changes: 116 additions & 19 deletions bins/validator-node/src/wasm_executor.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,57 @@
use anyhow::{Context, Result};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use tracing::{debug, info};
use wasm_runtime_interface::{
ExecPolicy, InMemoryStorageBackend, InstanceConfig, NetworkHostFunctions, NetworkPolicy,
RuntimeConfig, SandboxHostFunctions, SandboxPolicy, StorageHostConfig, TimePolicy, WasmModule,
WasmRuntime, WasmRuntimeError,
NoopStorageBackend, RuntimeConfig, SandboxHostFunctions, SandboxPolicy, StorageHostConfig,
StorageHostState, TimePolicy, WasmModule, WasmRuntime, WasmRuntimeError,
};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvaluationInput {
pub agent_data: Vec<u8>,
pub challenge_id: String,
pub params: Vec<u8>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvaluationOutput {
pub score: i64,
pub valid: bool,
pub message: String,
}

impl EvaluationOutput {
#[allow(dead_code)]
pub fn success(score: i64, message: &str) -> Self {
Self {
score,
valid: true,
message: String::from(message),
}
}

#[allow(dead_code)]
pub fn failure(message: &str) -> Self {
Self {
score: 0,
valid: false,
message: String::from(message),
}
}
}

pub struct WasmExecutorConfig {
pub module_dir: PathBuf,
pub max_memory_bytes: u64,
pub enable_fuel: bool,
pub fuel_limit: Option<u64>,
pub storage_host_config: StorageHostConfig,
}

impl Default for WasmExecutorConfig {
Expand All @@ -25,6 +61,7 @@ impl Default for WasmExecutorConfig {
max_memory_bytes: 512 * 1024 * 1024,
enable_fuel: false,
fuel_limit: None,
storage_host_config: StorageHostConfig::default(),
}
}
}
Expand Down Expand Up @@ -72,13 +109,17 @@ impl WasmChallengeExecutor {
&self,
module_path: &str,
network_policy: &NetworkPolicy,
input_data: &[u8],
) -> Result<(i64, ExecutionMetrics)> {
agent_data: &[u8],
challenge_id: &str,
params: &[u8],
) -> Result<(EvaluationOutput, ExecutionMetrics)> {
self.execute_evaluation_with_sandbox(
module_path,
network_policy,
&SandboxPolicy::default(),
input_data,
agent_data,
challenge_id,
params,
)
}

Expand All @@ -87,14 +128,25 @@ impl WasmChallengeExecutor {
module_path: &str,
network_policy: &NetworkPolicy,
sandbox_policy: &SandboxPolicy,
input_data: &[u8],
) -> Result<(i64, ExecutionMetrics)> {
agent_data: &[u8],
challenge_id: &str,
params: &[u8],
) -> Result<(EvaluationOutput, ExecutionMetrics)> {
let start = Instant::now();

let module = self
.load_module(module_path)
.context("Failed to load WASM module")?;

let input = EvaluationInput {
agent_data: agent_data.to_vec(),
challenge_id: challenge_id.to_string(),
params: params.to_vec(),
};

let serialized =
bincode::serialize(&input).context("Failed to serialize EvaluationInput")?;

let network_host_fns = Arc::new(NetworkHostFunctions::all());
let _sandbox_host_fns = Arc::new(SandboxHostFunctions::all());

Expand All @@ -105,7 +157,7 @@ impl WasmChallengeExecutor {
time_policy: TimePolicy::default(),
audit_logger: None,
memory_export: "memory".to_string(),
challenge_id: module_path.to_string(),
challenge_id: challenge_id.to_string(),
validator_id: "validator".to_string(),
restart_id: String::new(),
config_version: 0,
Expand All @@ -117,16 +169,22 @@ impl WasmChallengeExecutor {
.instantiate(&module, instance_config, Some(network_host_fns))
.map_err(|e| anyhow::anyhow!("WASM instantiation failed: {}", e))?;

let _storage_state = StorageHostState::new(
challenge_id.to_string(),
self.config.storage_host_config.clone(),
Arc::new(NoopStorageBackend),
);

let initial_fuel = instance.fuel_remaining();

let ptr = self.allocate_input(&mut instance, input_data)?;
let ptr = self.allocate_input(&mut instance, &serialized)?;

instance
.write_memory(ptr as usize, input_data)
.write_memory(ptr as usize, &serialized)
.map_err(|e| anyhow::anyhow!("Failed to write input data to WASM memory: {}", e))?;

let score = instance
.call_i32_i32_return_i64("evaluate", ptr, input_data.len() as i32)
let result = instance
.call_i32_i32_return_i64("evaluate", ptr, serialized.len() as i32)
.map_err(|e| match &e {
WasmRuntimeError::FuelExhausted => {
anyhow::anyhow!("WASM execution exceeded fuel limit")
Expand All @@ -137,6 +195,24 @@ impl WasmChallengeExecutor {
_ => anyhow::anyhow!("WASM evaluate call failed: {}", e),
})?;

let out_len = (result >> 32) as i32;
let out_ptr = result as i32;

if out_ptr == 0 && out_len == 0 {
return Err(anyhow::anyhow!(
"WASM evaluate returned null pointer, deserialization failed inside module"
));
}

let output_bytes = instance
.read_memory(out_ptr as usize, out_len as usize)
.map_err(|e| {
anyhow::anyhow!("Failed to read evaluation output from WASM memory: {}", e)
})?;

let output: EvaluationOutput = bincode::deserialize(&output_bytes)
.context("Failed to deserialize EvaluationOutput from WASM module")?;

let fuel_consumed = match (initial_fuel, instance.fuel_remaining()) {
(Some(initial), Some(remaining)) => Some(initial.saturating_sub(remaining)),
_ => None,
Expand All @@ -151,30 +227,44 @@ impl WasmChallengeExecutor {

info!(
module = module_path,
score,
challenge_id,
score = output.score,
valid = output.valid,
message = %output.message,
execution_time_ms = metrics.execution_time_ms,
memory_bytes = metrics.memory_used_bytes,
network_requests = metrics.network_requests_made,
fuel_consumed = ?metrics.fuel_consumed,
"WASM evaluation completed"
);

Ok((score, metrics))
Ok((output, metrics))
}

#[allow(dead_code)]
pub fn execute_validation(
&self,
module_path: &str,
network_policy: &NetworkPolicy,
input_data: &[u8],
agent_data: &[u8],
challenge_id: &str,
params: &[u8],
) -> Result<(bool, ExecutionMetrics)> {
let start = Instant::now();

let module = self
.load_module(module_path)
.context("Failed to load WASM module")?;

let input = EvaluationInput {
agent_data: agent_data.to_vec(),
challenge_id: challenge_id.to_string(),
params: params.to_vec(),
};

let serialized =
bincode::serialize(&input).context("Failed to serialize EvaluationInput")?;

let network_host_fns = Arc::new(NetworkHostFunctions::all());
let _sandbox_host_fns = Arc::new(SandboxHostFunctions::all());

Expand All @@ -185,7 +275,7 @@ impl WasmChallengeExecutor {
time_policy: TimePolicy::default(),
audit_logger: None,
memory_export: "memory".to_string(),
challenge_id: module_path.to_string(),
challenge_id: challenge_id.to_string(),
validator_id: "validator".to_string(),
restart_id: String::new(),
config_version: 0,
Expand All @@ -197,16 +287,22 @@ impl WasmChallengeExecutor {
.instantiate(&module, instance_config, Some(network_host_fns))
.map_err(|e| anyhow::anyhow!("WASM instantiation failed: {}", e))?;

let _storage_state = StorageHostState::new(
challenge_id.to_string(),
self.config.storage_host_config.clone(),
Arc::new(NoopStorageBackend),
);

let initial_fuel = instance.fuel_remaining();

let ptr = self.allocate_input(&mut instance, input_data)?;
let ptr = self.allocate_input(&mut instance, &serialized)?;

instance
.write_memory(ptr as usize, input_data)
.write_memory(ptr as usize, &serialized)
.map_err(|e| anyhow::anyhow!("Failed to write input data to WASM memory: {}", e))?;

let result = instance
.call_i32_i32_return_i32("validate", ptr, input_data.len() as i32)
.call_i32_i32_return_i32("validate", ptr, serialized.len() as i32)
.map_err(|e| match &e {
WasmRuntimeError::FuelExhausted => {
anyhow::anyhow!("WASM execution exceeded fuel limit")
Expand All @@ -233,6 +329,7 @@ impl WasmChallengeExecutor {

info!(
module = module_path,
challenge_id,
valid,
execution_time_ms = metrics.execution_time_ms,
memory_bytes = metrics.memory_used_bytes,
Expand Down
Loading
Loading