Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1,150 changes: 1,059 additions & 91 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions changelog/bragaigor-nit-4305.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Internal
- Add continuous mode to JIT validator
- Introduce `JitMachine` (equivalent to Go counterpart `JitMachine`)
- Introduce graceful shutdown through signals
2 changes: 1 addition & 1 deletion crates/validation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{
};

/// Counterpart to Go `validator.GoGlobalState`.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
pub struct GoGlobalState {
#[serde(with = "As::<DisplayFromStr>")]
Expand Down
6 changes: 5 additions & 1 deletion crates/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ axum = { workspace = true }
clap = { workspace = true, features = ["derive"] }
jit = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["rt-multi-thread"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util", "process", "signal"] }
tower-http = { workspace = true, features = ["trace"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = [
Expand All @@ -25,3 +26,6 @@ tracing-subscriber = { workspace = true, features = [
"env-filter",
] }
validation = { workspace = true }

[dev-dependencies]
reqwest = "0.13.1"
70 changes: 61 additions & 9 deletions crates/validator/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,69 @@
// Copyright 2025-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

//! Server Configuration and CLI Argument Parsing.
//!
//! This module defines the command-line interface (CLI) and configuration structures
//! for the validation server. It utilizes `clap` to parse arguments and environment variables
//! into strongly-typed configuration objects used throughout the application.

use anyhow::Result;
use arbutil::Bytes32;
use clap::{Args, Parser, ValueEnum};
use serde::{Deserialize, Serialize};
use std::fs::read_to_string;
use std::net::SocketAddr;
use std::path::PathBuf;

use crate::engine::config::JitMachineConfig;
use crate::engine::machine::JitMachine;

#[derive(Debug)]
pub struct ServerState {
pub mode: InputMode,
pub binary: PathBuf,
pub module_root: Bytes32,
/// jit machine responsible for computing next GlobalState. Not wrapped
/// in Arc<> since the caller of ServerState is wrapped in Arc<>
pub jit_machine: Option<JitMachine>,
pub available_workers: usize,
}

impl ServerState {
pub fn new(config: &ServerConfig) -> Result<Self> {
let available_workers = config.get_workers()?;
let module_root = config.get_module_root()?;
let jit_machine = match config.mode {
InputMode::Continuous => {
let config = JitMachineConfig::default();

let jit_machine = JitMachine::new(&config, Some(module_root))?;

Some(jit_machine)
}
InputMode::Native => None,
};
Ok(ServerState {
mode: config.mode,
binary: config.binary.clone(),
module_root,
jit_machine,
available_workers,
})
}
}

#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum InputMode {
Native,
Continuous,
}

#[derive(Copy, Clone, Debug, ValueEnum, Default)]
pub enum LoggingFormat {
#[default]
Text,
Json,
}
use tracing::warn;

const DEFAULT_NUM_WORKERS: usize = 4;
Expand All @@ -26,6 +82,10 @@ pub struct ServerConfig {
#[clap(long, value_enum, default_value_t = LoggingFormat::Text)]
pub logging_format: LoggingFormat,

/// Defines how Validator consumes input
#[clap(long, value_enum, default_value_t = InputMode::Native)]
pub mode: InputMode,

#[clap(flatten)]
module_root_config: ModuleRootConfig,

Expand Down Expand Up @@ -83,14 +143,6 @@ impl ServerConfig {
}
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, ValueEnum, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LoggingFormat {
#[default]
Text,
Json,
}

#[cfg(test)]
mod tests {
use clap::Parser;
Expand Down
26 changes: 26 additions & 0 deletions crates/validator/src/engine/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2025-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

// The default for JIT binary, no need for LLVM right now
pub(crate) const DEFAULT_JIT_CRANELIFT: bool = true;

pub(crate) const TARGET_ARM_64: &str = "arm64";
pub(crate) const TARGET_AMD_64: &str = "amd64";
pub(crate) const TARGET_HOST: &str = "host";

#[derive(Clone, Debug)]
pub struct JitMachineConfig {
pub prover_bin_path: String,
pub jit_cranelift: bool,
pub wasm_memory_usage_limit: u64,
}

impl Default for JitMachineConfig {
fn default() -> Self {
Self {
prover_bin_path: "replay.wasm".to_owned(),
jit_cranelift: DEFAULT_JIT_CRANELIFT,
wasm_memory_usage_limit: 1 << 32,
}
}
}
88 changes: 88 additions & 0 deletions crates/validator/src/engine/execution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2025-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

//! Validation Execution Logic and Request Models.
//!
//! This module serves as the central entry point for running validation tasks.
//! It defines the standard `ValidationRequest` structure used by the API and
//! implements the two primary validation strategies:
//!
//! 1. **Native Mode (`validate_native`):** Runs validation in-process using the
//! embedded `jit` crate. This utilizes the `jit::InputMode::Native` configuration
//! and is typically used for direct, low-overhead validation.
//!
//! 2. **Continuous Mode (`validate_continuous`):** Orchestrates an external "JIT Machine"
//! process (via `JitMachine`). This mode spawns a separate binary to handle
//! validation, isolating the execution environment and allowing for specific
//! binary version targeting.

use axum::Json;
use validation::{BatchInfo, GoGlobalState, ValidationInput};

use crate::{
config::ServerState, engine::config::DEFAULT_JIT_CRANELIFT, spawner_endpoints::local_target,
};

pub async fn validate_native(
server_state: &ServerState,
request: ValidationInput,
) -> Result<Json<GoGlobalState>, String> {
let delayed_inbox = match request.has_delayed_msg {
true => vec![BatchInfo {
number: request.delayed_msg_nr,
data: request.delayed_msg,
}],
false => vec![],
};

let opts = jit::Opts {
validator: jit::ValidatorOpts {
binary: server_state.binary.clone(),
cranelift: DEFAULT_JIT_CRANELIFT,
debug: false, // JIT's debug messages are using printlns, which would clutter the server logs
require_success: false, // Relevant for JIT binary only.
},
input_mode: jit::InputMode::Native(jit::NativeInput {
old_state: request.start_state.into(),
inbox: request.batch_info,
delayed_inbox,
preimages: request.preimages,
programs: request.user_wasms[local_target()].clone(),
}),
};

let result = jit::run(&opts).map_err(|error| format!("{error}"))?;
if let Some(err) = result.error {
Err(format!("{err}"))
} else {
Ok(Json(GoGlobalState::from(result.new_state)))
}
}

pub async fn validate_continuous(
server_state: &ServerState,
request: ValidationInput,
) -> Result<Json<GoGlobalState>, String> {
if server_state.jit_machine.is_none() {
return Err(format!(
"Jit machine is required continuous mode. Requested module root: {}",
server_state.module_root
));
}

let jit_machine = server_state.jit_machine.as_ref().unwrap();

if !jit_machine.is_active().await {
return Err(format!(
"Jit machine is not active. Maybe it received a shutdown signal? Requested module root: {}",
server_state.module_root
));
}

let new_state = jit_machine
.feed_machine(&request)
.await
.map_err(|error| format!("{error:?}"))?;

Ok(Json(new_state))
}
Loading
Loading