Skip to content
Draft
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
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static_assertions = { version = "1.1.0" }
structopt = { version = "0.3.26" }
thiserror = { version = "1.0.33" }
tiny-keccak = { version = "2.0.2" }
tokio = { version = "1.48.0" }
tokio = { version = "1.48.0", features = ["full"] }
tower-http = { version = "0.6.8" }
tracing = { version = "0.1.44" }
tracing-subscriber = { version = "0.3.22" }
Expand Down
40 changes: 13 additions & 27 deletions crates/validator/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
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;

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

#[derive(Clone, Debug, Parser)]
pub struct ServerConfig {
/// Socket address where the server should be run.
Expand All @@ -18,42 +23,23 @@ pub struct ServerConfig {
#[clap(long, value_enum, default_value_t = LoggingFormat::Text)]
pub logging_format: LoggingFormat,

#[clap(long, value_enum, default_value_t = InputMode::Native)]
pub mode: InputMode,

#[clap(flatten)]
module_root_config: ModuleRootConfig,
pub module_root_config: ModuleRootConfig,
}

#[derive(Clone, Debug, Args)]
#[group(required = true, multiple = false)]
struct ModuleRootConfig {
pub struct ModuleRootConfig {
/// Supported module root.
#[clap(long)]
module_root: Option<Bytes32>,
pub module_root: Option<Bytes32>,

/// Path to the file containing the module root.
#[clap(long)]
module_root_path: Option<PathBuf>,
}

impl ServerConfig {
pub fn get_module_root(&self) -> anyhow::Result<Bytes32> {
match (
self.module_root_config.module_root,
&self.module_root_config.module_root_path,
) {
(Some(root), None) => Ok(root),
(None, Some(ref path)) => {
let content = read_to_string(path)?;
let root = content
.trim()
.parse::<Bytes32>()
.map_err(|e| anyhow::anyhow!(e))?;
Ok(root)
}
_ => Err(anyhow::anyhow!(
"Either module_root or module_root_path must be specified"
)),
}
}
pub module_root_path: Option<PathBuf>,
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, ValueEnum, Deserialize, Serialize)]
Expand Down
2 changes: 2 additions & 0 deletions crates/validator/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod spawner_endpoints;
pub mod validate;
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
//! package. Their serialization is configured to match the Go side (by using `PascalCase` for
//! field names).

use crate::endpoints::validate::{validate_contiguous, validate_native, ValidationRequest};
use crate::ServerState;
use arbutil::{Bytes32, PreimageType};
use arbutil::Bytes32;
use axum::extract::State;
use axum::response::IntoResponse;
use axum::Json;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::info;

pub async fn capacity() -> impl IntoResponse {
"1" // TODO: Figure out max number of workers (optionally, make it configurable)
Expand All @@ -35,65 +36,26 @@ pub async fn stylus_archs() -> &'static str {
"host"
}

pub async fn validate(Json(request): Json<ValidationRequest>) -> Result<Json<GlobalState>, String> {
let delayed_inbox = match request.has_delayed_msg {
true => vec![jit::SequencerMessage {
number: request.delayed_msg_number,
data: request.delayed_msg,
}],
false => vec![],
};

let opts = jit::Opts {
validator: jit::ValidatorOpts {
binary: Default::default(),
cranelift: true, // The default for JIT binary, no need for LLVM right now
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.into_iter().map(Into::into).collect(),
delayed_inbox,
preimages: request.preimages,
programs: request.user_wasms[stylus_archs().await].clone(),
}),
};

let result = jit::run(&opts).map_err(|error| format!("{error}"))?;
if let Some(err) = result.error {
Err(format!("{err}"))
} else {
Ok(Json(GlobalState::from(result.new_state)))
pub async fn validate(
State(state): State<Arc<ServerState>>,
Json(request): Json<ValidationRequest>,
) -> Result<Json<GlobalState>, String> {
match state.mode {
crate::config::InputMode::Native => validate_native(request, &state.locator).await,
crate::config::InputMode::Continuous => validate_contiguous(request, &state.locator).await,
}
}

pub async fn wasm_module_roots(State(state): State<Arc<ServerState>>) -> impl IntoResponse {
format!("[{:?}]", state.module_root)
}

/// Counterpart for Go struct `validator.ValidationInput`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct ValidationRequest {
id: u64,
has_delayed_msg: bool,
#[serde(rename = "DelayedMsgNr")]
delayed_msg_number: u64,
preimages: HashMap<PreimageType, HashMap<Bytes32, Vec<u8>>>,
user_wasms: HashMap<String, HashMap<Bytes32, Vec<u8>>>,
batch_info: Vec<BatchInfo>,
delayed_msg: Vec<u8>,
start_state: GlobalState,
debug_chain: bool,
format!("{:?}", state.locator.module_roots())
}

/// Counterpart for Go struct `validator.BatchInfo`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct BatchInfo {
number: u64,
data: Vec<u8>,
pub number: u64,
pub data: Vec<u8>,
}

impl From<BatchInfo> for jit::SequencerMessage {
Expand All @@ -106,13 +68,13 @@ impl From<BatchInfo> for jit::SequencerMessage {
}

/// Counterpart for Go struct `validator.GoGlobalState`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "PascalCase")]
pub struct GlobalState {
block_hash: Bytes32,
send_root: Bytes32,
batch: u64,
pos_in_batch: u64,
pub block_hash: Bytes32,
pub send_root: Bytes32,
pub batch: u64,
pub pos_in_batch: u64,
}

impl From<GlobalState> for jit::GlobalState {
Expand Down
96 changes: 96 additions & 0 deletions crates/validator/src/endpoints/validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::collections::HashMap;

use arbutil::{Bytes32, PreimageType};
use axum::Json;
use serde::{Deserialize, Serialize};

use crate::{
endpoints::spawner_endpoints::{stylus_archs, BatchInfo, GlobalState},
server_jit::{
config::JitMachineConfig, jit_machine::JitMachine, machine_locator::MachineLocator,
},
};

/// Counterpart for Go struct `validator.ValidationInput`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct ValidationRequest {
id: u64,
pub has_delayed_msg: bool,
#[serde(rename = "DelayedMsgNr")]
pub delayed_msg_number: u64,
pub preimages: HashMap<PreimageType, HashMap<Bytes32, Vec<u8>>>,
pub user_wasms: HashMap<String, HashMap<Bytes32, Vec<u8>>>,
pub batch_info: Vec<BatchInfo>,
pub delayed_msg: Vec<u8>,
pub start_state: GlobalState,
pub module_root: Bytes32,
debug_chain: bool,
}

pub async fn validate_native(
request: ValidationRequest,
locator: &MachineLocator,
) -> Result<Json<GlobalState>, String> {
let delayed_inbox = match request.has_delayed_msg {
true => vec![jit::SequencerMessage {
number: request.delayed_msg_number,
data: request.delayed_msg,
}],
false => vec![],
};
let config = JitMachineConfig::default();

let opts = jit::Opts {
validator: jit::ValidatorOpts {
binary: locator
.get_machine_path(request.module_root)
.join(&config.prover_bin_path),
cranelift: true, // The default for JIT binary, no need for LLVM right now
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.into_iter().map(Into::into).collect(),
delayed_inbox,
preimages: request.preimages,
programs: request.user_wasms[stylus_archs().await].clone(),
}),
};

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

pub async fn validate_contiguous(
request: ValidationRequest,
locator: &MachineLocator,
) -> Result<Json<GlobalState>, String> {
let config = JitMachineConfig::default();
let module_root = if request.module_root == Bytes32::default() {
None
} else {
Some(request.module_root)
};

let mut jit_machine =
JitMachine::new(&config, locator, module_root).map_err(|error| format!("{error:?}"))?;

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

// Make sure JIT validator binary is done
jit_machine
.complete_machine()
.await
.map_err(|error| format!("{error:?}"))?;

Ok(Json(new_state))
}
14 changes: 10 additions & 4 deletions crates/validator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

use anyhow::Result;
use arbutil::Bytes32;
use clap::Parser;
use logging::init_logging;
use router::create_router;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::info;

use crate::{config::InputMode, server_jit::machine_locator::MachineLocator};

mod config;
mod endpoints;
mod logging;
mod router;
mod spawner_endpoints;
mod server_jit;

#[derive(Clone, Debug)]
pub struct ServerState {
module_root: Bytes32,
mode: InputMode,
locator: MachineLocator,
}

#[tokio::main]
Expand All @@ -26,8 +29,11 @@ async fn main() -> Result<()> {
init_logging(config.logging_format)?;
info!("Starting validator server with config: {:#?}", config);

let locator = MachineLocator::new(&config.module_root_config)?;

let state = Arc::new(ServerState {
module_root: config.get_module_root()?,
mode: config.mode,
locator,
});

let listener = TcpListener::bind(config.address).await?;
Expand Down
2 changes: 1 addition & 1 deletion crates/validator/src/router.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2025-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

use crate::{spawner_endpoints, ServerState};
use crate::{endpoints::spawner_endpoints, ServerState};
use axum::routing::{get, post};
use axum::Router;
use std::sync::Arc;
Expand Down
Loading
Loading