Skip to content

Commit 21e4b11

Browse files
authored
Add support for multiple module roots for Validator (#4310)
* Allow multiple module roots support for Validator Allow Rust Validator to to run multiple jit machines in parallel. That way it can handle validate requests for different module roots Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * add changelog Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * manually impl FromStr for PreimageType Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * use drain for complete_machines and fix continuous mode Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * update changelog Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * update jit_manager comment Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * make clippy happy Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * Fix delayed_messages bug on send_validation_input Make jit_manager non Optional and have empty process for native mode. Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> * remove redundant is_active() Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com> --------- Signed-off-by: Igor Braga <5835477+bragaigor@users.noreply.github.com>
1 parent 7b3a90c commit 21e4b11

File tree

16 files changed

+325
-185
lines changed

16 files changed

+325
-185
lines changed

.github/workflows/_go-tests.yml

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -150,39 +150,71 @@ jobs:
150150
JIT_HASH=$(echo "$OUTPUT" | grep -oP 'hash \K[a-f0-9]+')
151151
echo "jit_hash=$JIT_HASH" >> $GITHUB_OUTPUT
152152
153-
- name: run the Rust validation server on block input json
153+
- name: run Rust validation server (Native & Continuous)
154154
if: inputs.run-defaults-a
155155
run: |
156-
# 1. Start server in background
156+
# 0. Build once
157157
make build-validation-server
158-
target/bin/validator --module-root-path ./target/machines/latest/module-root.txt &
159-
SERVER_PID=$!
160158
161-
# 2. Wait for server to respond (up to 5 seconds)
162-
echo "Waiting for validator to start..."
163-
timeout 5s bash -c 'until curl -s localhost:4141 > /dev/null; do sleep 1; done'
164-
165-
# 3. Send validation request
166-
RESPONSE=$(curl -s -X POST http://localhost:4141/validation_validate \
167-
-H "Content-Type: application/json" \
168-
-d @target/TestProgramStorage/block_inputs.json)
169-
170-
# 4. Stop the server
171-
kill $SERVER_PID
172-
173-
# 5. Compare hashes
174-
SERVER_HASH=$(echo "$RESPONSE" | jq -r '.BlockHash')
159+
# Capture the expected hash once
175160
JIT_HASH="${{ steps.jit.outputs.jit_hash }}"
161+
162+
validate_mode() {
163+
local MODE_NAME=$1
164+
local SERVER_ARGS=$2
165+
166+
echo "::group::Testing Mode: $MODE_NAME"
167+
168+
# 1. Start server in background
169+
echo "Starting server ($MODE_NAME)..."
170+
target/bin/validator --module-root-path ./target/machines/latest/module-root.txt $SERVER_ARGS &
171+
SERVER_PID=$!
172+
173+
# 2. Wait for server to respond (up to 5 seconds)
174+
echo "Waiting for validator ($MODE_NAME) to start..."
175+
if ! timeout 5s bash -c 'until curl -s localhost:4141 > /dev/null; do sleep 1; done'; then
176+
echo "❌ Server ($MODE_NAME) failed to start within timeout"
177+
kill $SERVER_PID
178+
exit 1
179+
fi
180+
181+
# 3. Send 2 validation requests
182+
RESPONSE1=$(curl -s -X POST http://localhost:4141/validation_validate \
183+
-H "Content-Type: application/json" \
184+
-d @target/TestProgramStorage/block_inputs.json)
185+
186+
RESPONSE2=$(curl -s -X POST http://localhost:4141/validation_validate \
187+
-H "Content-Type: application/json" \
188+
-d @target/TestProgramStorage/block_inputs.json)
189+
190+
# 4. Stop the server
191+
kill $SERVER_PID
192+
wait $SERVER_PID 2>/dev/null || true
193+
194+
# 5. Compare hashes
195+
SERVER_HASH1=$(echo "$RESPONSE1" | jq -r '.BlockHash')
196+
SERVER_HASH2=$(echo "$RESPONSE2" | jq -r '.BlockHash')
197+
198+
echo "JIT Hash: $JIT_HASH"
199+
echo "Server response 1 Hash: $SERVER_HASH1"
200+
echo "Server response 2 Hash: $SERVER_HASH2"
201+
202+
if [ "$JIT_HASH" == "$SERVER_HASH1" ] && [ "$JIT_HASH" == "$SERVER_HASH2" ]; then
203+
echo "✅ $MODE_NAME Validation successful"
204+
else
205+
echo "❌ $MODE_NAME Validation failed: hashes do not match"
206+
exit 1
207+
fi
208+
echo "::endgroup::"
209+
}
210+
211+
# --- RUN TESTS ---
176212
177-
echo "JIT Hash: $JIT_HASH"
178-
echo "Server Hash: $SERVER_HASH"
179-
180-
if [ "$JIT_HASH" == "$SERVER_HASH" ]; then
181-
echo "✅ Validation successful"
182-
else
183-
echo "❌ Validation failed: hashes do not match"
184-
exit 1
185-
fi
213+
# Run Validator in Native mode
214+
validate_mode "Native" ""
215+
216+
# Run Validator in Continuous mode
217+
validate_mode "Continuous" "--mode continuous"
186218
187219
# --------------------- FLAKY MODE --------------------------
188220

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.

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ stylus_files = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(wasm_li
104104
jit_dir = crates/jit
105105
jit_files = $(wildcard $(jit_dir)/*.toml $(jit_dir)/*.rs $(jit_dir)/src/*.rs $(jit_dir)/src/*/*.rs) $(stylus_files)
106106

107+
validation_crate_dir = crates/validation
108+
validation_files = $(wildcard $(validation_crate_dir)/*.toml $(validation_crate_dir)/src/*.rs $(validation_crate_dir)/src/*/*.rs) $(rust_arbutil_files)
109+
110+
validator_dir = crates/validator
111+
validator_files = $(wildcard $(validator_dir)/*.toml $(validator_dir)/src/*.rs $(validator_dir)/src/*/*.rs) $(jit_files) $(validation_files)
112+
107113
wasm32_wasi = target/wasm32-wasip1/release
108114
wasm32_unknown = target/wasm32-unknown-unknown/release
109115

@@ -376,7 +382,7 @@ $(arbitrator_jit): $(DEP_PREDICATE) $(jit_files)
376382
cargo build --release -p jit ${CARGOFLAGS}
377383
install target/release/jit $@
378384

379-
$(validation_server): $(DEP_PREDICATE)
385+
$(validation_server): $(DEP_PREDICATE) $(validator_files)
380386
mkdir -p `dirname $(validation_server)`
381387
cargo build --release -p validator ${CARGOFLAGS}
382388
install target/release/validator $@

changelog/bragaigor-nit-4390.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Internal
2+
- Add support for multiple module roots for Validator
3+
- Fix Validator continuous mode to run jit binary from inside tokio runtime

crates/arbutil/src/types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ pub enum PreimageType {
3535
DACertificate = 3,
3636
}
3737

38+
impl FromStr for PreimageType {
39+
type Err = String;
40+
fn from_str(s: &str) -> Result<Self, Self::Err> {
41+
match s {
42+
"Keccak256" | "0" => Ok(PreimageType::Keccak256),
43+
"Sha2_256" | "1" => Ok(PreimageType::Sha2_256),
44+
"EthVersionedHash" | "2" => Ok(PreimageType::EthVersionedHash),
45+
"DACertificate" | "3" => Ok(PreimageType::DACertificate),
46+
_ => Err(format!("unknown preimage type: {s}")),
47+
}
48+
}
49+
}
50+
3851
/// cbindgen:field-names=[bytes]
3952
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
4053
#[repr(C)]

crates/validation/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use arbutil::{Bytes32, PreimageType};
44
use brotli::BrotliStatus;
55
use serde::{Deserialize, Serialize};
6-
use serde_with::{base64::Base64, As, DisplayFromStr, TryFromInto};
6+
use serde_with::{base64::Base64, As, DisplayFromStr};
77
use std::{
88
collections::HashMap,
99
io::{self, BufRead},
@@ -89,7 +89,7 @@ pub struct ValidationInput {
8989
pub delayed_msg_nr: u64,
9090
#[serde(
9191
rename = "PreimagesB64",
92-
with = "As::<HashMap<TryFromInto<u8>, HashMap<Base64, Base64>>>"
92+
with = "As::<HashMap<DisplayFromStr, HashMap<Base64, Base64>>>"
9393
)]
9494
pub preimages: PreimageMap,
9595
pub batch_info: Vec<BatchInfo>,

crates/validation/src/transfer/sender.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ use std::io::{Error, Write};
1111
pub fn send_validation_input(writer: &mut impl Write, input: &ValidationInput) -> IOResult<()> {
1212
send_global_state(writer, &input.start_state)?;
1313
send_batches(writer, &input.batch_info)?;
14-
if let Some(batch) = input.delayed_msg() {
15-
send_batches(writer, &[batch])?;
16-
}
14+
let batch = input.delayed_msg().map(|b| [b]).unwrap_or_default();
15+
send_batches(writer, &batch)?;
1716
send_preimages(writer, &input.preimages)?;
1817
send_user_wasms(writer, &input.user_wasms)?;
1918
finish_sending(writer)

crates/validator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ clap = { workspace = true, features = ["derive"] }
1717
jit = { workspace = true }
1818
serde = { workspace = true, features = ["derive"] }
1919
serde_json = { workspace = true }
20+
serde_with = { workspace = true }
2021
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util", "process", "signal"] }
2122
tower-http = { workspace = true, features = ["trace"] }
2223
tracing = { workspace = true }

crates/validator/src/config.rs

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,43 @@
88
//! into strongly-typed configuration objects used throughout the application.
99
1010
use anyhow::Result;
11-
use arbutil::Bytes32;
1211
use clap::{Args, Parser, ValueEnum};
1312
use std::fs::read_to_string;
1413
use std::net::SocketAddr;
1514
use std::path::PathBuf;
1615

17-
use crate::engine::config::JitMachineConfig;
18-
use crate::engine::machine::JitMachine;
16+
use crate::engine::config::{JitManagerConfig, ModuleRoot};
17+
use crate::engine::machine::JitProcessManager;
1918

2019
#[derive(Debug)]
2120
pub struct ServerState {
2221
pub mode: InputMode,
2322
pub binary: PathBuf,
24-
pub module_root: Bytes32,
25-
/// jit machine responsible for computing next GlobalState. Not wrapped
26-
/// in Arc<> since the caller of ServerState is wrapped in Arc<>
27-
pub jit_machine: Option<JitMachine>,
23+
pub module_root: ModuleRoot,
24+
/// Jit manager responsible for computing next GlobalState. Not wrapped
25+
/// in Arc<> since the caller of ServerState is wrapped in Arc<>. This field
26+
/// is optional because it's only available in continuous InputMode
27+
pub jit_manager: JitProcessManager,
2828
pub available_workers: usize,
2929
}
3030

3131
impl ServerState {
32-
pub fn new(config: &ServerConfig) -> Result<Self> {
33-
let available_workers = config.get_workers()?;
32+
pub fn new(config: &ServerConfig, available_workers: usize) -> Result<Self> {
33+
// TODO: Load multiple module roots via MachineLocator (NIT-4346)
3434
let module_root = config.get_module_root()?;
35-
let jit_machine = match config.mode {
36-
InputMode::Continuous => {
37-
let config = JitMachineConfig::default();
38-
39-
let jit_machine = JitMachine::new(&config, Some(module_root))?;
40-
41-
Some(jit_machine)
42-
}
43-
InputMode::Native => None,
35+
let manager_config = JitManagerConfig {
36+
prover_bin_path: config.binary.clone(),
37+
..Default::default()
38+
};
39+
let jit_manager = match config.mode {
40+
InputMode::Continuous => JitProcessManager::new(&manager_config, module_root)?,
41+
InputMode::Native => JitProcessManager::new_empty(&manager_config),
4442
};
4543
Ok(ServerState {
4644
mode: config.mode,
4745
binary: config.binary.clone(),
4846
module_root,
49-
jit_machine,
47+
jit_manager,
5048
available_workers,
5149
})
5250
}
@@ -98,15 +96,15 @@ pub struct ServerConfig {
9896
struct ModuleRootConfig {
9997
/// Supported module root.
10098
#[clap(long)]
101-
module_root: Option<Bytes32>,
99+
module_root: Option<ModuleRoot>,
102100

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

108106
impl ServerConfig {
109-
pub fn get_module_root(&self) -> anyhow::Result<Bytes32> {
107+
pub fn get_module_root(&self) -> anyhow::Result<ModuleRoot> {
110108
match (
111109
self.module_root_config.module_root,
112110
&self.module_root_config.module_root_path,
@@ -116,7 +114,7 @@ impl ServerConfig {
116114
let content = read_to_string(path)?;
117115
let root = content
118116
.trim()
119-
.parse::<Bytes32>()
117+
.parse::<ModuleRoot>()
120118
.map_err(|e| anyhow::anyhow!(e))?;
121119
Ok(root)
122120
}

crates/validator/src/engine/config.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
// Copyright 2025-2026, Offchain Labs, Inc.
22
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md
33

4+
use std::path::PathBuf;
5+
6+
use arbutil::Bytes32;
7+
48
// The default for JIT binary, no need for LLVM right now
59
pub(crate) const DEFAULT_JIT_CRANELIFT: bool = true;
610

7-
pub(crate) const TARGET_ARM_64: &str = "arm64";
8-
pub(crate) const TARGET_AMD_64: &str = "amd64";
9-
pub(crate) const TARGET_HOST: &str = "host";
11+
pub type ModuleRoot = Bytes32;
1012

1113
#[derive(Clone, Debug)]
12-
pub struct JitMachineConfig {
13-
pub prover_bin_path: String,
14+
pub struct JitManagerConfig {
15+
pub prover_bin_path: PathBuf,
1416
pub jit_cranelift: bool,
1517
pub wasm_memory_usage_limit: u64,
1618
}
1719

18-
impl Default for JitMachineConfig {
20+
impl Default for JitManagerConfig {
1921
fn default() -> Self {
2022
Self {
21-
prover_bin_path: "replay.wasm".to_owned(),
23+
prover_bin_path: "replay.wasm".into(),
2224
jit_cranelift: DEFAULT_JIT_CRANELIFT,
2325
wasm_memory_usage_limit: 1 << 32,
2426
}

0 commit comments

Comments
 (0)