From 9c1731173b942cea6212509170f015065b9f9302 Mon Sep 17 00:00:00 2001 From: Ollie202 Date: Sat, 9 May 2026 08:20:11 +0100 Subject: [PATCH 1/3] feat(network-monitor): compile counter program and note script at build time Replaces runtime MASM compilation (CodeBuilder + include_str!) with build-time compilation via build.rs. The counter program is compiled to a .masl library and the increment note script to a serialized NoteScript binary, both embedded via include_bytes!. This catches protocol breakage at cargo build time rather than silently at startup. Closes #1831. --- CHANGELOG.md | 3 + bin/network-monitor/Cargo.toml | 4 ++ bin/network-monitor/build.rs | 49 ++++++++++++++++ .../src/assets/counter_program.masm | 2 - .../src/assets/increment_counter.masm | 2 +- bin/network-monitor/src/counter.rs | 29 +++------- bin/network-monitor/src/deploy/counter.rs | 56 +++++++++++-------- 7 files changed, 98 insertions(+), 47 deletions(-) create mode 100644 bin/network-monitor/build.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1fdb784c..1ffcdc59a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ - Replaced the network monitor's JavaScript dashboard with a server-rendered Maud + HTMX frontend ([#2024](https://github.com/0xMiden/node/pull/2024)). - [BREAKING] Removed `CheckNullifiers` endpoint ([#2049](https://github.com/0xMiden/node/pull/2049)). - [BREAKING] `BlockRange.block_to` is now required for all RPC endpoints ([#2056](https://github.com/0xMiden/node/pull/2056)). +- Compile network monitor counter program and note script at build time using `build.rs`, catching protocol breakage at compile time rather than at startup ([#1831](https://github.com/0xMiden/node/issues/1831)). +- Replaced blocking-in-async operations in the validator, remote prover, and ntx-builder with `spawn_blocking` to avoid starving the Tokio runtime ([#2041](https://github.com/0xMiden/node/pull/2041)). +- Implement persistent RocksDB backend for `AccountStateForest`, improving startup time ([#2020](https://github.com/0xMiden/node/pull/2020)). ## v0.14.10 (2026-05-29) diff --git a/bin/network-monitor/Cargo.toml b/bin/network-monitor/Cargo.toml index 7889ac9bad..7567ff75f3 100644 --- a/bin/network-monitor/Cargo.toml +++ b/bin/network-monitor/Cargo.toml @@ -39,3 +39,7 @@ tonic = { features = ["codegen", "tls-native-roots", "transport"], wo tonic-health = { workspace = true } tracing = { workspace = true } url = { features = ["serde"], workspace = true } + +[build-dependencies] +fs-err = { workspace = true } +miden-protocol = { features = ["std"], workspace = true } diff --git a/bin/network-monitor/build.rs b/bin/network-monitor/build.rs new file mode 100644 index 0000000000..9457d6d1ab --- /dev/null +++ b/bin/network-monitor/build.rs @@ -0,0 +1,49 @@ +use std::env; +use std::path::Path; +use std::sync::Arc; + +use miden_protocol::assembly::diagnostics::NamedSource; +use miden_protocol::note::NoteScript; +use miden_protocol::transaction::TransactionKernel; +use miden_protocol::utils::serde::Serializable; + +const COUNTER_MODULE_PATH: &str = "miden::monitor::counter_contract"; + +fn main() { + println!("cargo::rerun-if-changed=src/assets/counter_program.masm"); + println!("cargo::rerun-if-changed=src/assets/increment_counter.masm"); + + let out_dir_str = env::var("OUT_DIR").expect("OUT_DIR must be set"); + let out_dir = Path::new(&out_dir_str); + + let counter_masm = std::fs::read_to_string("src/assets/counter_program.masm") + .expect("src/assets/counter_program.masm must exist"); + let note_masm = std::fs::read_to_string("src/assets/increment_counter.masm") + .expect("src/assets/increment_counter.masm must exist"); + + let assembler = TransactionKernel::assembler().with_warnings_as_errors(true); + + // Compile counter program to a library (.masl). + let counter_lib = assembler + .clone() + .assemble_library([NamedSource::new(COUNTER_MODULE_PATH, counter_masm)]) + .expect("counter_program.masm should compile without errors"); + + counter_lib + .write_to_file(out_dir.join("counter_program.masl")) + .expect("writing counter_program.masl should succeed"); + + // Compile note script statically linked against the counter library. + let mut note_assembler = assembler; + note_assembler + .link_static_library(Arc::as_ref(&counter_lib)) + .expect("linking counter library into note assembler should succeed"); + + let note_program = note_assembler + .assemble_program(note_masm) + .expect("increment_counter.masm should compile without errors"); + + let note_script_bytes = NoteScript::new(note_program).to_bytes(); + fs_err::write(out_dir.join("increment_note_script.bin"), note_script_bytes) + .expect("writing increment_note_script.bin should succeed"); +} diff --git a/bin/network-monitor/src/assets/counter_program.masm b/bin/network-monitor/src/assets/counter_program.masm index 9f8679da93..4175cffce0 100644 --- a/bin/network-monitor/src/assets/counter_program.masm +++ b/bin/network-monitor/src/assets/counter_program.masm @@ -8,8 +8,6 @@ use miden::protocol::active_account use miden::protocol::native_account use miden::protocol::active_note use miden::protocol::account_id -use miden::protocol::tx - const COUNTER_SLOT = word("miden::monitor::counter_contract::counter") const OWNER_SLOT = word("miden::monitor::counter_contract::owner") diff --git a/bin/network-monitor/src/assets/increment_counter.masm b/bin/network-monitor/src/assets/increment_counter.masm index 4a835bdd8b..e2b92f1afa 100644 --- a/bin/network-monitor/src/assets/increment_counter.masm +++ b/bin/network-monitor/src/assets/increment_counter.masm @@ -2,7 +2,7 @@ # This script is executed as a note and calls the # `counter_contract::increment` entrypoint. -use external_contract::counter_contract +use miden::monitor::counter_contract begin call.counter_contract::increment diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 3d8c56b0f1..8c8242438c 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -4,8 +4,8 @@ //! of the network account deployed at startup by creating and submitting network notes. use std::path::Path; -use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, LazyLock}; use std::time::{Duration, Instant}; use anyhow::{Context, Result}; @@ -31,7 +31,6 @@ use miden_protocol::transaction::{InputNotes, PartialBlockchain, TransactionArgs use miden_protocol::utils::serde::{Deserializable, Serializable}; use miden_protocol::{Felt, Word}; use miden_standards::account::interface::{AccountInterface, AccountInterfaceExt}; -use miden_standards::code_builder::CodeBuilder; use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint}; use miden_tx::auth::BasicAuthenticator; use miden_tx::{LocalTransactionProver, TransactionExecutor}; @@ -65,6 +64,11 @@ const REGENERATE_COOLDOWN: Duration = Duration::from_secs(3600); // SHARED STATE // ================================================================================================ +static INCREMENT_NOTE_SCRIPT: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/increment_note_script.bin")); + NoteScript::read_from_bytes(bytes).expect("increment note script should be valid") +}); + #[derive(Debug, Default, Clone)] pub struct LatencyState { pending: Option, @@ -567,7 +571,7 @@ async fn setup_increment_task( let block_header = get_genesis_block_header(rpc_client).await?; - let increment_script = create_increment_script()?; + let increment_script = INCREMENT_NOTE_SCRIPT.clone(); let mut data_store = MonitorDataStore::new(block_header.clone(), PartialBlockchain::default()); data_store.add_account(wallet_account.clone()); @@ -917,25 +921,6 @@ fn load_counter_account(file_path: &Path) -> Result { Ok(account_file.account.clone()) } -/// Create the increment procedure script. -fn create_increment_script() -> Result { - let script = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/assets/counter_program.masm")); - - let script_builder = CodeBuilder::new() - .with_linked_module("external_contract::counter_contract", script) - .context("Failed to create script builder with library")?; - - let note_script = script_builder - .compile_note_script(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/assets/increment_counter.masm" - ))) - .context("Failed to compile note script")?; - - Ok(note_script) -} - /// Create a network note that targets the counter account. fn create_network_note( wallet_account: &Account, diff --git a/bin/network-monitor/src/deploy/counter.rs b/bin/network-monitor/src/deploy/counter.rs index 5479f15898..c2069b07f3 100644 --- a/bin/network-monitor/src/deploy/counter.rs +++ b/bin/network-monitor/src/deploy/counter.rs @@ -15,9 +15,10 @@ use miden_protocol::account::{ StorageSlot, StorageSlotName, }; +use miden_protocol::assembly::Library; +use miden_protocol::utils::serde::Deserializable; use miden_protocol::utils::sync::LazyLock; use miden_protocol::{Felt, Word}; -use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::account_component::IncrNonceAuthComponent; use tracing::instrument; @@ -33,39 +34,50 @@ pub static COUNTER_SLOT_NAME: LazyLock = LazyLock::new(|| { .expect("storage slot name should be valid") }); -/// Create a counter program account with custom MASM script. -#[instrument(target = COMPONENT, name = "create-counter-account", skip_all, ret(level = "debug"))] -pub fn create_counter_account(owner_account_id: AccountId) -> Result { - // Load and customize the MASM script - let script = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/assets/counter_program.masm")); +static COUNTER_PROGRAM_LIBRARY: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/counter_program.masl")); + Library::read_from_bytes(bytes).expect("counter program library should be valid") +}); + +/// An [`AccountComponent`] implementing the counter contract used by the network monitor. +pub struct CounterComponent { + pub owner_account_id: AccountId, +} - // Compile the account code - let owner_account_id_prefix = owner_account_id.prefix().as_felt(); - let owner_account_id_suffix = owner_account_id.suffix(); +impl From for AccountComponent { + fn from(component: CounterComponent) -> Self { + let owner_account_id_prefix = component.owner_account_id.prefix().as_felt(); + let owner_account_id_suffix = component.owner_account_id.suffix(); - let owner_id_slot = StorageSlot::with_value( - OWNER_SLOT_NAME.clone(), - Word::from([owner_account_id_suffix, owner_account_id_prefix, Felt::ZERO, Felt::ZERO]), - ); + let owner_id_slot = StorageSlot::with_value( + OWNER_SLOT_NAME.clone(), + Word::from([owner_account_id_suffix, owner_account_id_prefix, Felt::ZERO, Felt::ZERO]), + ); - let counter_slot = StorageSlot::with_value(COUNTER_SLOT_NAME.clone(), Word::empty()); + let counter_slot = StorageSlot::with_value(COUNTER_SLOT_NAME.clone(), Word::empty()); - let component_code = - CodeBuilder::default().compile_component_code("counter::program", script)?; + let metadata = AccountComponentMetadata::new("counter::program", AccountType::all()); - let metadata = AccountComponentMetadata::new("counter::program", AccountType::all()); - let account_code = - AccountComponent::new(component_code, vec![counter_slot, owner_id_slot], metadata)?; + AccountComponent::new( + COUNTER_PROGRAM_LIBRARY.clone(), + vec![counter_slot, owner_id_slot], + metadata, + ) + .expect("counter component should be valid") + } +} +/// Create a counter program account. +#[instrument(target = COMPONENT, name = "create-counter-account", skip_all, ret(level = "debug"))] +pub fn create_counter_account(owner_account_id: AccountId) -> Result { + let counter_component: AccountComponent = CounterComponent { owner_account_id }.into(); let incr_nonce_auth: AccountComponent = IncrNonceAuthComponent.into(); - // Create the counter program account let init_seed: [u8; 32] = rand::random(); let counter_account = AccountBuilder::new(init_seed) .account_type(AccountType::RegularAccountUpdatableCode) .storage_mode(AccountStorageMode::Network) - .with_component(account_code) + .with_component(counter_component) .with_auth_component(incr_nonce_auth) .build()?; From f0a2e9bd5b524df55dd1b35c68531f2fee55835a Mon Sep 17 00:00:00 2001 From: Ollie202 Date: Sat, 9 May 2026 10:34:34 +0100 Subject: [PATCH 2/3] chore: update Cargo.lock for miden-network-monitor build dependency --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index eb5f37ee88..054a1a7047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,6 +3200,7 @@ dependencies = [ "anyhow", "axum", "clap", + "fs-err", "hex", "humantime", "maud", From 573615680a5d47004b6b4336900c483dff88517d Mon Sep 17 00:00:00 2001 From: Ollie202 Date: Sat, 9 May 2026 11:16:26 +0100 Subject: [PATCH 3/3] fix(network-monitor): use fs_err::read_to_string in build.rs --- bin/network-monitor/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/network-monitor/build.rs b/bin/network-monitor/build.rs index 9457d6d1ab..34cf2ba0db 100644 --- a/bin/network-monitor/build.rs +++ b/bin/network-monitor/build.rs @@ -16,9 +16,9 @@ fn main() { let out_dir_str = env::var("OUT_DIR").expect("OUT_DIR must be set"); let out_dir = Path::new(&out_dir_str); - let counter_masm = std::fs::read_to_string("src/assets/counter_program.masm") + let counter_masm = fs_err::read_to_string("src/assets/counter_program.masm") .expect("src/assets/counter_program.masm must exist"); - let note_masm = std::fs::read_to_string("src/assets/increment_counter.masm") + let note_masm = fs_err::read_to_string("src/assets/increment_counter.masm") .expect("src/assets/increment_counter.masm must exist"); let assembler = TransactionKernel::assembler().with_warnings_as_errors(true);