From d35952333f9ced6d37bfe686890819862eb606ea Mon Sep 17 00:00:00 2001 From: djole Date: Mon, 6 Apr 2026 16:10:02 +0200 Subject: [PATCH 01/10] feat: enable DAP-backed transaction script debugging Add support for interactive debugging of transaction scripts via the Debug Adapter Protocol (DAP). This includes: - `--start-debug-adapter` flag on the `exec` CLI command - `execute_program_with_dap` method in the client - Offline bootstrap mode (`--offline` flag) for creating accounts and executing programs without a node connection - Optional `dap` feature gate on `miden-client` and `miden-client-cli` --- Cargo.lock | 123 ++++++++++++++++++++ Cargo.toml | 17 +++ bin/miden-cli/Cargo.toml | 4 + bin/miden-cli/src/commands/exec.rs | 82 +++++++++++-- bin/miden-cli/src/commands/new_account.rs | 20 ++++ crates/rust-client/Cargo.toml | 2 + crates/rust-client/src/sync/block_header.rs | 92 ++++++++++++++- crates/rust-client/src/transaction/mod.rs | 105 ++++++++++++----- 8 files changed, 403 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da95c2c456..46ca16ef13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2927,6 +2927,7 @@ dependencies = [ "getrandom 0.3.4", "gloo-timers", "hex", + "miden-debug", "miden-node-proto-build", "miden-note-transport-proto-build", "miden-protocol", @@ -2982,6 +2983,7 @@ dependencies = [ "figment", "miden-client", "miden-client-sqlite-store", + "miden-debug", "miette", "predicates", "rand 0.9.2", @@ -3163,6 +3165,71 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "miden-debug" +version = "0.5.0" +dependencies = [ + "clap", + "futures", + "glob", + "log", + "miden-assembly", + "miden-assembly-syntax", + "miden-core", + "miden-crypto", + "miden-debug-dap", + "miden-debug-engine", + "miden-debug-types", + "miden-mast-package", + "miden-processor", + "miden-protocol", + "miden-thiserror", + "miden-tx", + "num-traits", + "rustc-demangle", + "serde", + "serde_json", + "smallvec", + "socket2 0.5.10", + "tokio", + "tokio-util", + "toml 0.8.23", +] + +[[package]] +name = "miden-debug-dap" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "miden-debug-engine" +version = "0.1.0" +dependencies = [ + "clap", + "glob", + "log", + "miden-assembly", + "miden-assembly-syntax", + "miden-core", + "miden-debug-dap", + "miden-debug-types", + "miden-mast-package", + "miden-processor", + "miden-thiserror", + "miden-tx", + "num-traits", + "rustc-demangle", + "serde", + "serde_json", + "smallvec", + "socket2 0.5.10", + "toml 0.8.23", +] + [[package]] name = "miden-debug-types" version = "0.22.1" @@ -3741,6 +3808,26 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "miden-thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183ff8de338956ecfde3a38573241eb7a6f3d44d73866c210e5629c07fa00253" +dependencies = [ + "miden-thiserror-impl", +] + +[[package]] +name = "miden-thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "miden-tx" version = "0.14.3" @@ -6219,6 +6306,7 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ + "indexmap", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -7444,3 +7532,38 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[patch.unused]] +name = "miden-air" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" + +[[patch.unused]] +name = "miden-core" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" + +[[patch.unused]] +name = "miden-debug-types" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" + +[[patch.unused]] +name = "miden-processor" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" + +[[patch.unused]] +name = "miden-utils-core-derive" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" + +[[patch.unused]] +name = "miden-utils-diagnostics" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" + +[[patch.unused]] +name = "miden-utils-indexing" +version = "0.22.0" +source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" diff --git a/Cargo.toml b/Cargo.toml index 0ee7c63402..e105123844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,12 @@ miden-node-validator = { version = "0.14" } miden-note-transport-proto-build = { default-features = false, version = "0.2" } miden-remote-prover-client = { default-features = false, features = ["tx-prover"], version = "0.14" } +# Miden debug dependency +miden-debug = { default-features = false, features = [ + "dap", + "std", +], path = "../miden-debug" } + # External dependencies anyhow = { default-features = false, version = "1.0" } async-trait = { version = "0.1" } @@ -79,3 +85,14 @@ module_name_repetitions = "allow" # Many triggers, and is a stylistic choice must_use_candidate = "allow" # This marks many fn's which isn't helpful. should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. + +# Patch VM crates to miden-vm main for unreleased `into_parts()` needed by miden-debug DAP +# TODO: This will go. +[patch.crates-io] +miden-air = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } +miden-core = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } +miden-debug-types = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } +miden-processor = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } +miden-utils-core-derive = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } +miden-utils-diagnostics = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } +miden-utils-indexing = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } diff --git a/bin/miden-cli/Cargo.toml b/bin/miden-cli/Cargo.toml index 6958911ede..0f2ecfd2d9 100644 --- a/bin/miden-cli/Cargo.toml +++ b/bin/miden-cli/Cargo.toml @@ -16,10 +16,14 @@ version.workspace = true name = "miden-client" path = "src/main.rs" +[features] +dap = ["dep:miden-debug", "miden-client/dap"] + [dependencies] # Workspace dependencies miden-client = { features = ["tonic"], workspace = true } miden-client-sqlite-store = { workspace = true } +miden-debug = { optional = true, workspace = true } # External dependencies clap = { features = ["derive"], version = "4.5" } diff --git a/bin/miden-cli/src/commands/exec.rs b/bin/miden-cli/src/commands/exec.rs index fdd57b9e4f..4dfd96ce57 100644 --- a/bin/miden-cli/src/commands/exec.rs +++ b/bin/miden-cli/src/commands/exec.rs @@ -2,7 +2,9 @@ use std::collections::BTreeMap; use std::path::PathBuf; use clap::Parser; +use miden_client::account::AccountId; use miden_client::keystore::Keystore; +use miden_client::transaction::{ForeignAccount, TransactionScript}; use miden_client::vm::AdviceInputs; use miden_client::{Client, Felt, Word}; use serde::{Deserialize, Deserializer, Serialize, de}; @@ -47,6 +49,13 @@ pub struct ExecCmd { /// Print the output stack grouped into words #[arg(long, default_value_t = false)] hex_words: bool, + + /// Start a DAP debug adapter server on the given address (e.g. "127.0.0.1:4711") + /// and wait for a DAP client to connect before executing. + /// + /// If this binary was built without DAP support, using this flag returns an error. + #[arg(long = "start-debug-adapter")] + start_debug_adapter: Option, } impl ExecCmd { @@ -87,19 +96,13 @@ impl ExecCmd { let tx_script = client.code_builder().compile_tx_script(&program)?; - let result = client - .execute_program(account_id, tx_script, advice_inputs, BTreeMap::new()) - .await; + let output_stack = + self.execute_program(&mut client, account_id, tx_script, advice_inputs).await?; - match result { - Ok(output_stack) => { - println!("Program executed successfully"); - println!("Output stack:"); - self.print_stack(output_stack); - Ok(()) - }, - Err(err) => Err(CliError::Exec(err.into(), "error executing the program".to_string())), - } + println!("Program executed successfully"); + println!("Output stack:"); + self.print_stack(output_stack); + Ok(()) } /// Print the output stack in a human-readable format @@ -130,6 +133,61 @@ impl ExecCmd { } } } + + async fn execute_program( + &self, + client: &mut Client, + account_id: AccountId, + tx_script: TransactionScript, + advice_inputs: AdviceInputs, + ) -> Result<[Felt; 16], CliError> { + let foreign_accounts = BTreeMap::::new(); + + #[cfg(feature = "dap")] + if let Some(addr) = self.start_debug_adapter.as_ref() { + let config = miden_debug::DapConfig::new(addr.clone()); + let config_handle = config.clone(); + miden_debug::DapConfig::set_global(config); + + let script_path = PathBuf::from(&self.script_path); + loop { + let program = std::fs::read_to_string(&script_path)?; + let tx_script = client.code_builder().compile_tx_script(&program)?; + + let result = client + .execute_program_with_dap( + account_id, + tx_script, + advice_inputs.clone(), + foreign_accounts.clone(), + ) + .await; + + if config_handle.restart_requested() { + config_handle.reset_restart(); + eprintln!("Recompiling from source and restarting debug session..."); + continue; + } + + return result.map_err(|err| { + CliError::Exec(err.into(), "error executing the program".to_string()) + }); + } + } + + #[cfg(not(feature = "dap"))] + if self.start_debug_adapter.is_some() { + return Err(CliError::InvalidArgument( + "--start-debug-adapter requires a CLI build with the `dap` feature enabled" + .to_string(), + )); + } + + client + .execute_program(account_id, tx_script, advice_inputs, foreign_accounts) + .await + .map_err(|err| CliError::Exec(err.into(), "error executing the program".to_string())) + } } // INPUT FILE PROCESSING diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index 53170eb40b..3ae378bead 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -98,6 +98,9 @@ pub struct NewWalletCmd { /// authentication transaction. #[arg(long, default_value_t = false)] pub deploy: bool, + /// Seed local-only state so the wallet can be created and used for execution without a node. + #[arg(long, default_value_t = false)] + pub offline: bool, } impl NewWalletCmd { @@ -126,6 +129,7 @@ impl NewWalletCmd { &package_paths, self.init_storage_data_path.clone(), self.deploy, + self.offline, ) .await?; @@ -193,6 +197,9 @@ pub struct NewAccountCmd { /// authentication transaction. #[arg(long, default_value_t = false)] pub deploy: bool, + /// Seed local-only state so the account can be created and used for execution without a node. + #[arg(long, default_value_t = false)] + pub offline: bool, } impl NewAccountCmd { @@ -209,6 +216,7 @@ impl NewAccountCmd { &self.packages, self.init_storage_data_path.clone(), self.deploy, + self.offline, ) .await?; @@ -385,6 +393,7 @@ async fn create_client_account( package_paths: &[PathBuf], init_storage_data_path: Option, deploy: bool, + offline: bool, ) -> Result { if package_paths.is_empty() { return Err(CliError::InvalidArgument(format!( @@ -393,6 +402,12 @@ async fn create_client_account( ", client_binary_name().display()))); } + if deploy && offline { + return Err(CliError::InvalidArgument( + "`--offline` cannot be combined with `--deploy`".to_string(), + )); + } + // Load the component templates and initialization storage data. let cli_config = CliConfig::load()?; @@ -455,6 +470,11 @@ async fn create_client_account( println!("Using custom authentication component from package (no key generated)."); } + if offline { + client.prepare_offline_bootstrap().await?; + println!("Offline mode seeded default RPC limits and a synthetic genesis header."); + } + client.add_account(&account, false).await?; if deploy { diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index 7233971883..801935ad5f 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -24,6 +24,7 @@ ignored = ["getrandom", "prost-types", "tonic-prost"] crate-type = ["lib"] [features] +dap = ["dep:miden-debug"] default = ["std"] std = [ "dep:tempfile", @@ -41,6 +42,7 @@ tonic = [] [dependencies] # Miden dependencies +miden-debug = { optional = true, workspace = true } miden-protocol = { workspace = true } miden-remote-prover-client = { default-features = false, features = ["tx-prover"], workspace = true } miden-standards = { workspace = true } diff --git a/crates/rust-client/src/sync/block_header.rs b/crates/rust-client/src/sync/block_header.rs index 5ed4330e8a..b94b029b0f 100644 --- a/crates/rust-client/src/sync/block_header.rs +++ b/crates/rust-client/src/sync/block_header.rs @@ -2,15 +2,25 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_protocol::Word; -use miden_protocol::block::{BlockHeader, BlockNumber}; +use miden_protocol::account::AccountId; +use miden_protocol::block::account_tree::AccountTree; +use miden_protocol::block::nullifier_tree::NullifierTree; +use miden_protocol::block::{BlockHeader, BlockNoteTree, BlockNumber, Blockchain, FeeParameters}; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::crypto::merkle::MerklePath; use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, MmrPeaks, PartialMmr}; +use miden_protocol::crypto::merkle::smt::Smt; +use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; use tracing::warn; use crate::rpc::NodeRpcClient; use crate::store::{BlockRelevance, StoreError}; use crate::{Client, ClientError}; +/// Synthetic faucet account ID used only by the offline bootstrap genesis header so fee +/// parameters can reference a stable native-asset faucet without requiring node state. +const OFFLINE_NATIVE_ASSET_FAUCET_ID: u128 = 0xab00_0000_0000_cd20_0000_ac00_0000_de00; + /// Network information management methods. impl Client { /// Retrieves a block header by its block number from the store. @@ -43,6 +53,30 @@ impl Client { Ok(()) } + /// Seeds the local client state needed to create accounts and execute programs without a node. + /// + /// This stores default RPC limits and inserts a synthetic genesis header if one is not + /// already present in the store. The synthetic header is only intended for local-only + /// execution and debugging. + pub async fn prepare_offline_bootstrap(&mut self) -> Result<(), ClientError> { + let limits = self.store.get_rpc_limits().await?.unwrap_or_default(); + self.store.set_rpc_limits(limits).await?; + self.rpc_api.set_rpc_limits(limits).await; + + if let Some((genesis, _)) = self.store.get_block_header_by_num(BlockNumber::GENESIS).await? + { + self.rpc_api.set_genesis_commitment(genesis.commitment()).await?; + return Ok(()); + } + + let genesis = synthetic_offline_genesis_header(); + let blank_mmr_peaks = MmrPeaks::new(Forest::empty(), vec![]) + .expect("Blank MmrPeaks should not fail to instantiate"); + self.store.insert_block_header(&genesis, blank_mmr_peaks, false).await?; + self.rpc_api.set_genesis_commitment(genesis.commitment()).await?; + Ok(()) + } + /// Fetches from the store the current view of the chain's [`PartialMmr`]. pub async fn get_current_partial_mmr(&self) -> Result { self.store.get_current_partial_mmr().await.map_err(Into::into) @@ -86,6 +120,30 @@ impl Client { } } +fn synthetic_offline_genesis_header() -> BlockHeader { + let native_asset_id = AccountId::try_from(OFFLINE_NATIVE_ASSET_FAUCET_ID) + .expect("offline native asset faucet ID should be valid"); + let fee_parameters = + FeeParameters::new(native_asset_id, 500).expect("offline fee params should be valid"); + let validator_key = SecretKey::with_rng(&mut rand::rng()).public_key(); + let transactions = OrderedTransactionHeaders::new_unchecked(Vec::new()); + + BlockHeader::new( + 0, + Word::empty(), + BlockNumber::GENESIS, + Blockchain::new().commitment(), + AccountTree::::default().root(), + NullifierTree::::default().root(), + BlockNoteTree::empty().root(), + transactions.commitment(), + TransactionKernel.to_commitment(), + validator_key, + fee_parameters, + 0, + ) +} + // UTILS // -------------------------------------------------------------------------------------------- @@ -156,13 +214,22 @@ pub(crate) async fn fetch_block_header( #[cfg(test)] mod tests { - use miden_protocol::block::{BlockHeader, BlockNumber}; + use alloc::vec::Vec; + + use miden_protocol::block::account_tree::AccountTree; + use miden_protocol::block::nullifier_tree::NullifierTree; + use miden_protocol::block::{BlockHeader, BlockNoteTree, BlockNumber, Blockchain}; use miden_protocol::crypto::merkle::MerklePath; use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, Mmr, PartialMmr}; - use miden_protocol::transaction::TransactionKernel; + use miden_protocol::crypto::merkle::smt::Smt; + use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; use miden_protocol::{Felt, Word}; - use super::{adjust_merkle_path_for_forest, authenticated_block_nodes}; + use super::{ + adjust_merkle_path_for_forest, + authenticated_block_nodes, + synthetic_offline_genesis_header, + }; fn word(n: u64) -> Word { Word::new([Felt::new(n), Felt::new(0), Felt::new(0), Felt::new(0)]) @@ -281,4 +348,21 @@ mod tests { assert_eq!(nodes[0], (InOrderIndex::from_leaf_pos(4), block_header.commitment())); assert_eq!(&nodes[1..], path_nodes.as_slice()); } + + #[test] + fn synthetic_offline_genesis_header_matches_empty_chain_state() { + let genesis = synthetic_offline_genesis_header(); + + assert_eq!(genesis.block_num(), BlockNumber::GENESIS); + assert_eq!(genesis.prev_block_commitment(), Word::empty()); + assert_eq!(genesis.chain_commitment(), Blockchain::new().commitment()); + assert_eq!(genesis.account_root(), AccountTree::::default().root()); + assert_eq!(genesis.nullifier_root(), NullifierTree::::default().root()); + assert_eq!(genesis.note_root(), BlockNoteTree::empty().root()); + assert_eq!( + genesis.tx_commitment(), + OrderedTransactionHeaders::new_unchecked(Vec::new()).commitment() + ); + assert_eq!(genesis.tx_kernel_commitment(), TransactionKernel.to_commitment()); + } } diff --git a/crates/rust-client/src/transaction/mod.rs b/crates/rust-client/src/transaction/mod.rs index d7c50224d3..7219760268 100644 --- a/crates/rust-client/src/transaction/mod.rs +++ b/crates/rust-client/src/transaction/mod.rs @@ -478,36 +478,30 @@ where advice_inputs: AdviceInputs, foreign_accounts: BTreeMap, ) -> Result<[Felt; 16], ClientError> { - let (fpi_block_number, foreign_account_inputs) = - self.retrieve_foreign_account_inputs(foreign_accounts).await?; + let (data_store, block_ref) = + self.prepare_program_execution(account_id, foreign_accounts).await?; - let block_ref = if let Some(block_number) = fpi_block_number { - block_number - } else { - self.get_sync_height().await? - }; - - let account_record = self - .store - .get_account(account_id) - .await? - .ok_or(ClientError::AccountDataNotFound(account_id))?; - - let account: Account = account_record.try_into()?; - - let data_store = ClientDataStore::new(self.store.clone(), self.rpc_api.clone()); - - data_store.register_foreign_account_inputs(foreign_account_inputs.iter().cloned()); - - // Ensure code is loaded on MAST store - data_store.mast_store().load_account_code(account.code()); + Ok(self + .build_executor(&data_store)? + .execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs) + .await?) + } - for fpi_account in &foreign_account_inputs { - data_store.mast_store().load_account_code(fpi_account.code()); - } + /// Executes the provided transaction script with a DAP debug adapter listening for + /// connections, allowing interactive debugging via any DAP-compatible client. + #[cfg(feature = "dap")] + pub async fn execute_program_with_dap( + &mut self, + account_id: AccountId, + tx_script: TransactionScript, + advice_inputs: AdviceInputs, + foreign_accounts: BTreeMap, + ) -> Result<[Felt; 16], ClientError> { + let (data_store, block_ref) = + self.prepare_program_execution(account_id, foreign_accounts).await?; Ok(self - .build_executor(&data_store)? + .build_dap_executor(&data_store)? .execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs) .await?) } @@ -797,6 +791,45 @@ where Ok((Some(block_num), return_foreign_account_inputs)) } + /// Prepares the data store and block reference for program execution. + /// + /// This is shared setup for both `execute_program` and `execute_program_with_dap`. + async fn prepare_program_execution( + &mut self, + account_id: AccountId, + foreign_accounts: BTreeMap, + ) -> Result<(ClientDataStore, BlockNumber), ClientError> { + let (fpi_block_number, foreign_account_inputs) = + self.retrieve_foreign_account_inputs(foreign_accounts).await?; + + let block_ref = if let Some(block_number) = fpi_block_number { + block_number + } else { + self.get_sync_height().await? + }; + + let account_record = self + .store + .get_account(account_id) + .await? + .ok_or(ClientError::AccountDataNotFound(account_id))?; + + let account: Account = account_record.try_into()?; + + let data_store = ClientDataStore::new(self.store.clone(), self.rpc_api.clone()); + + data_store.register_foreign_account_inputs(foreign_account_inputs.iter().cloned()); + + // Ensure code is loaded on MAST store + data_store.mast_store().load_account_code(account.code()); + + for fpi_account in &foreign_account_inputs { + data_store.mast_store().load_account_code(fpi_account.code()); + } + + Ok((data_store, block_ref)) + } + /// Creates a transaction executor configured with the client's runtime options, /// authenticator, and source manager. pub(crate) fn build_executor<'store, 'auth, STORE: DataStore + Sync>( @@ -811,6 +844,26 @@ where Ok(executor) } + + /// Creates a transaction executor configured for DAP (Debug Adapter Protocol) debugging. + #[cfg(feature = "dap")] + pub(crate) fn build_dap_executor<'store, 'auth, STORE: DataStore + Sync>( + &'auth self, + data_store: &'store STORE, + ) -> Result< + TransactionExecutor<'store, 'auth, STORE, AUTH, miden_debug::DapExecutor>, + TransactionExecutorError, + > { + let mut executor = TransactionExecutor::new(data_store) + .with_program_executor::() + .with_options(self.exec_options)?; + if let Some(authenticator) = self.authenticator.as_deref() { + executor = executor.with_authenticator(authenticator); + } + executor = executor.with_source_manager(self.source_manager.clone()); + + Ok(executor) + } } // HELPERS From 4e1960931361590238515a46a0a9dc9d3d8cf7db Mon Sep 17 00:00:00 2001 From: djole Date: Wed, 8 Apr 2026 14:44:33 +0200 Subject: [PATCH 02/10] fixup: VM v0.22.1 and protocol v0.14.3 compatibility, enable DAP by default - Use miden-vm v0.22.1 and protocol v0.14.3 from crates.io - Enable DAP feature by default in rust-client and CLI - Update Package construction for v0.22.1 API --- bin/miden-cli/Cargo.toml | 1 + crates/rust-client/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/miden-cli/Cargo.toml b/bin/miden-cli/Cargo.toml index 0f2ecfd2d9..78fb86c82b 100644 --- a/bin/miden-cli/Cargo.toml +++ b/bin/miden-cli/Cargo.toml @@ -17,6 +17,7 @@ name = "miden-client" path = "src/main.rs" [features] +default = ["dap"] dap = ["dep:miden-debug", "miden-client/dap"] [dependencies] diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index 801935ad5f..bbdba8760a 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -25,7 +25,7 @@ crate-type = ["lib"] [features] dap = ["dep:miden-debug"] -default = ["std"] +default = ["std", "dap"] std = [ "dep:tempfile", "dep:tokio", From 053e1d1e108d3605d72ded8eee4097d3120aead2 Mon Sep 17 00:00:00 2001 From: djole Date: Wed, 8 Apr 2026 14:44:38 +0200 Subject: [PATCH 03/10] chore: use miden-debug v0.6 from crates.io Replace local path dependency with published crate. --- Cargo.lock | 47 +++++++++-------------------------------------- Cargo.toml | 12 +----------- 2 files changed, 10 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46ca16ef13..86d6c7ff37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3167,7 +3167,9 @@ dependencies = [ [[package]] name = "miden-debug" -version = "0.5.0" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df04a684eeb96efabc63e2800a01945f7f6e19ffd00d3b49064e85f7e1fac444" dependencies = [ "clap", "futures", @@ -3198,7 +3200,9 @@ dependencies = [ [[package]] name = "miden-debug-dap" -version = "0.1.0" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cd41176322df12836bb4deecd4b619f7cf8239ed7b2c4ac1da7b2830e5199c" dependencies = [ "serde", "serde_json", @@ -3207,7 +3211,9 @@ dependencies = [ [[package]] name = "miden-debug-engine" -version = "0.1.0" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e03dd00bd4dab99dbfdec9fd07009811a7e3b3a988e74a28c6ccb735ac34e138" dependencies = [ "clap", "glob", @@ -7532,38 +7538,3 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[patch.unused]] -name = "miden-air" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" - -[[patch.unused]] -name = "miden-core" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" - -[[patch.unused]] -name = "miden-debug-types" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" - -[[patch.unused]] -name = "miden-processor" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" - -[[patch.unused]] -name = "miden-utils-core-derive" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" - -[[patch.unused]] -name = "miden-utils-diagnostics" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" - -[[patch.unused]] -name = "miden-utils-indexing" -version = "0.22.0" -source = "git+https://github.com/0xMiden/miden-vm.git?rev=2327f4a5c04c7a6ec3aa612612a3fc993d383dc3#2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" diff --git a/Cargo.toml b/Cargo.toml index e105123844..9ab1b15986 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ miden-remote-prover-client = { default-features = false, features = ["tx-p miden-debug = { default-features = false, features = [ "dap", "std", -], path = "../miden-debug" } +], version = "0.6" } # External dependencies anyhow = { default-features = false, version = "1.0" } @@ -86,13 +86,3 @@ must_use_candidate = "allow" # This marks many fn's which isn't helpful should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. -# Patch VM crates to miden-vm main for unreleased `into_parts()` needed by miden-debug DAP -# TODO: This will go. -[patch.crates-io] -miden-air = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } -miden-core = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } -miden-debug-types = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } -miden-processor = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } -miden-utils-core-derive = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } -miden-utils-diagnostics = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } -miden-utils-indexing = { git = "https://github.com/0xMiden/miden-vm.git", rev = "2327f4a5c04c7a6ec3aa612612a3fc993d383dc3" } From 029769779d581d85d37d04276a82c065fb36a415 Mon Sep 17 00:00:00 2001 From: djole Date: Wed, 8 Apr 2026 17:09:50 +0200 Subject: [PATCH 04/10] fixup: address review comments - Use SocketAddr instead of String for --start-debug-adapter flag - Gate --start-debug-adapter behind #[cfg(feature = "dap")] so it only appears when the feature is enabled - Remove trailing empty line in workspace Cargo.toml --- CHANGELOG.md | 2 ++ Cargo.toml | 1 - bin/miden-cli/src/commands/exec.rs | 15 +++------------ 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce5b89ec0..4ba956c183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ ### Enhancements +* Added DAP-backed transaction script debugging support with `--start-debug-adapter` flag on the `exec` CLI command, `execute_program_with_dap` client method, and offline bootstrap mode for node-less execution ([#1959](https://github.com/0xMiden/miden-client/pull/1959)). + * Made `GrpcNoteTransportClient` connection lazy, deferring it to the first RPC call instead of connecting eagerly at client initialization ([#1970](https://github.com/0xMiden/miden-client/pull/1970)). * Updated the `GrpcClient` to fetch the RPC limits from the node ([#1724](https://github.com/0xMiden/miden-client/pull/1724)) ([#1737](https://github.com/0xMiden/miden-client/pull/1737), [#1809](https://github.com/0xMiden/miden-client/pull/1809)). * Added typed error parsing for node RPC endpoints, enabling programmatic error handling instead of string parsing ([#1734](https://github.com/0xMiden/miden-client/pull/1734)). diff --git a/Cargo.toml b/Cargo.toml index 9ab1b15986..8d835f1cc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,4 +85,3 @@ module_name_repetitions = "allow" # Many triggers, and is a stylistic choice must_use_candidate = "allow" # This marks many fn's which isn't helpful. should_panic_without_expect = "allow" # We don't care about the specific panic message. # End of pedantic lints. - diff --git a/bin/miden-cli/src/commands/exec.rs b/bin/miden-cli/src/commands/exec.rs index 4dfd96ce57..e139f86129 100644 --- a/bin/miden-cli/src/commands/exec.rs +++ b/bin/miden-cli/src/commands/exec.rs @@ -52,10 +52,9 @@ pub struct ExecCmd { /// Start a DAP debug adapter server on the given address (e.g. "127.0.0.1:4711") /// and wait for a DAP client to connect before executing. - /// - /// If this binary was built without DAP support, using this flag returns an error. + #[cfg(feature = "dap")] #[arg(long = "start-debug-adapter")] - start_debug_adapter: Option, + start_debug_adapter: Option, } impl ExecCmd { @@ -145,7 +144,7 @@ impl ExecCmd { #[cfg(feature = "dap")] if let Some(addr) = self.start_debug_adapter.as_ref() { - let config = miden_debug::DapConfig::new(addr.clone()); + let config = miden_debug::DapConfig::new(addr.to_string()); let config_handle = config.clone(); miden_debug::DapConfig::set_global(config); @@ -175,14 +174,6 @@ impl ExecCmd { } } - #[cfg(not(feature = "dap"))] - if self.start_debug_adapter.is_some() { - return Err(CliError::InvalidArgument( - "--start-debug-adapter requires a CLI build with the `dap` feature enabled" - .to_string(), - )); - } - client .execute_program(account_id, tx_script, advice_inputs, foreign_accounts) .await From cfd63a6b34422abd2123e2f95b834b4e6e132f7b Mon Sep 17 00:00:00 2001 From: djole Date: Wed, 8 Apr 2026 17:30:51 +0200 Subject: [PATCH 05/10] fixup: gate --offline behind testing feature --- bin/miden-cli/Cargo.toml | 1 + bin/miden-cli/src/commands/new_account.rs | 14 +++++++++++++- crates/rust-client/src/sync/block_header.rs | 19 ++++++++++++------- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/bin/miden-cli/Cargo.toml b/bin/miden-cli/Cargo.toml index 78fb86c82b..c8d3a1367b 100644 --- a/bin/miden-cli/Cargo.toml +++ b/bin/miden-cli/Cargo.toml @@ -19,6 +19,7 @@ path = "src/main.rs" [features] default = ["dap"] dap = ["dep:miden-debug", "miden-client/dap"] +testing = ["miden-client/testing"] [dependencies] # Workspace dependencies diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index 3ae378bead..d2864fc533 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -99,6 +99,8 @@ pub struct NewWalletCmd { #[arg(long, default_value_t = false)] pub deploy: bool, /// Seed local-only state so the wallet can be created and used for execution without a node. + /// Only available when built with the `testing` feature. + #[cfg(feature = "testing")] #[arg(long, default_value_t = false)] pub offline: bool, } @@ -129,7 +131,10 @@ impl NewWalletCmd { &package_paths, self.init_storage_data_path.clone(), self.deploy, + #[cfg(feature = "testing")] self.offline, + #[cfg(not(feature = "testing"))] + false, ) .await?; @@ -198,6 +203,8 @@ pub struct NewAccountCmd { #[arg(long, default_value_t = false)] pub deploy: bool, /// Seed local-only state so the account can be created and used for execution without a node. + /// Only available when built with the `testing` feature. + #[cfg(feature = "testing")] #[arg(long, default_value_t = false)] pub offline: bool, } @@ -216,7 +223,10 @@ impl NewAccountCmd { &self.packages, self.init_storage_data_path.clone(), self.deploy, + #[cfg(feature = "testing")] self.offline, + #[cfg(not(feature = "testing"))] + false, ) .await?; @@ -393,7 +403,7 @@ async fn create_client_account( package_paths: &[PathBuf], init_storage_data_path: Option, deploy: bool, - offline: bool, + #[cfg_attr(not(feature = "testing"), allow(unused))] offline: bool, ) -> Result { if package_paths.is_empty() { return Err(CliError::InvalidArgument(format!( @@ -402,6 +412,7 @@ async fn create_client_account( ", client_binary_name().display()))); } + #[cfg(feature = "testing")] if deploy && offline { return Err(CliError::InvalidArgument( "`--offline` cannot be combined with `--deploy`".to_string(), @@ -470,6 +481,7 @@ async fn create_client_account( println!("Using custom authentication component from package (no key generated)."); } + #[cfg(feature = "testing")] if offline { client.prepare_offline_bootstrap().await?; println!("Offline mode seeded default RPC limits and a synthetic genesis header."); diff --git a/crates/rust-client/src/sync/block_header.rs b/crates/rust-client/src/sync/block_header.rs index b94b029b0f..9a6d60942f 100644 --- a/crates/rust-client/src/sync/block_header.rs +++ b/crates/rust-client/src/sync/block_header.rs @@ -2,15 +2,9 @@ use alloc::sync::Arc; use alloc::vec::Vec; use miden_protocol::Word; -use miden_protocol::account::AccountId; -use miden_protocol::block::account_tree::AccountTree; -use miden_protocol::block::nullifier_tree::NullifierTree; -use miden_protocol::block::{BlockHeader, BlockNoteTree, BlockNumber, Blockchain, FeeParameters}; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::crypto::merkle::MerklePath; use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, MmrPeaks, PartialMmr}; -use miden_protocol::crypto::merkle::smt::Smt; -use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; use tracing::warn; use crate::rpc::NodeRpcClient; @@ -19,6 +13,7 @@ use crate::{Client, ClientError}; /// Synthetic faucet account ID used only by the offline bootstrap genesis header so fee /// parameters can reference a stable native-asset faucet without requiring node state. +#[cfg(feature = "testing")] const OFFLINE_NATIVE_ASSET_FAUCET_ID: u128 = 0xab00_0000_0000_cd20_0000_ac00_0000_de00; /// Network information management methods. @@ -58,6 +53,7 @@ impl Client { /// This stores default RPC limits and inserts a synthetic genesis header if one is not /// already present in the store. The synthetic header is only intended for local-only /// execution and debugging. + #[cfg(feature = "testing")] pub async fn prepare_offline_bootstrap(&mut self) -> Result<(), ClientError> { let limits = self.store.get_rpc_limits().await?.unwrap_or_default(); self.store.set_rpc_limits(limits).await?; @@ -120,7 +116,16 @@ impl Client { } } +#[cfg(feature = "testing")] fn synthetic_offline_genesis_header() -> BlockHeader { + use miden_protocol::account::AccountId; + use miden_protocol::block::account_tree::AccountTree; + use miden_protocol::block::nullifier_tree::NullifierTree; + use miden_protocol::block::{BlockNoteTree, Blockchain, FeeParameters}; + use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; + use miden_protocol::crypto::merkle::smt::Smt; + use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; + let native_asset_id = AccountId::try_from(OFFLINE_NATIVE_ASSET_FAUCET_ID) .expect("offline native asset faucet ID should be valid"); let fee_parameters = From 230760fdef44b479528ac26f26e42f99da7491e5 Mon Sep 17 00:00:00 2001 From: djole Date: Thu, 9 Apr 2026 12:17:58 +0200 Subject: [PATCH 06/10] fixup: apply taplo formatting --- Cargo.toml | 5 +---- bin/miden-cli/Cargo.toml | 2 +- crates/rust-client/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d835f1cc4..2b12f96129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,10 +56,7 @@ miden-note-transport-proto-build = { default-features = false, version = "0.2" } miden-remote-prover-client = { default-features = false, features = ["tx-prover"], version = "0.14" } # Miden debug dependency -miden-debug = { default-features = false, features = [ - "dap", - "std", -], version = "0.6" } +miden-debug = { default-features = false, features = ["dap", "std"], version = "0.6" } # External dependencies anyhow = { default-features = false, version = "1.0" } diff --git a/bin/miden-cli/Cargo.toml b/bin/miden-cli/Cargo.toml index c8d3a1367b..f1fdb1e0c8 100644 --- a/bin/miden-cli/Cargo.toml +++ b/bin/miden-cli/Cargo.toml @@ -17,8 +17,8 @@ name = "miden-client" path = "src/main.rs" [features] +dap = ["dep:miden-debug", "miden-client/dap"] default = ["dap"] -dap = ["dep:miden-debug", "miden-client/dap"] testing = ["miden-client/testing"] [dependencies] diff --git a/crates/rust-client/Cargo.toml b/crates/rust-client/Cargo.toml index bbdba8760a..562d1469fc 100644 --- a/crates/rust-client/Cargo.toml +++ b/crates/rust-client/Cargo.toml @@ -25,7 +25,7 @@ crate-type = ["lib"] [features] dap = ["dep:miden-debug"] -default = ["std", "dap"] +default = ["dap", "std"] std = [ "dep:tempfile", "dep:tokio", From 938dbb52577f10587acd1281269b9dca38443f1a Mon Sep 17 00:00:00 2001 From: djole Date: Fri, 10 Apr 2026 12:29:33 +0200 Subject: [PATCH 07/10] docs: add DAP debugging documentation --- docs/external/src/rust-client/debugging.md | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/external/src/rust-client/debugging.md diff --git a/docs/external/src/rust-client/debugging.md b/docs/external/src/rust-client/debugging.md new file mode 100644 index 0000000000..99c7580a5a --- /dev/null +++ b/docs/external/src/rust-client/debugging.md @@ -0,0 +1,93 @@ +--- +title: DAP Debugging +sidebar_position: 7 +--- + +# DAP Debugging + +The Miden client supports interactive debugging via the [Debug Adapter Protocol (DAP)](https://microsoft.github.io/debug-adapter-protocol/). You can debug both raw Miden Assembly scripts and Rust programs compiled to Miden via `midenc`. This lets you step through execution, set breakpoints, and inspect stack/memory state using any DAP-compatible client (e.g. VS Code, the `miden-debug` TUI). + +## Feature Flags + +Two feature flags control debugging support: + +| Feature | Crate | What it enables | +|---------|-------|-----------------| +| `dap` | `miden-client`, `miden-client-cli` | Compiles in DAP support (`execute_program_with_dap`, `--start-debug-adapter` CLI flag). **Enabled by default.** | +| `testing` | `miden-client-cli` | Enables the `--offline` flag on `new-wallet`/`new-account` commands for node-less account creation. Not available in production builds. | + +### Building with features + +```bash +# Default build (DAP enabled) +cargo build -p miden-client-cli + +# With offline mode for testing +cargo build -p miden-client-cli --features testing + +# Without DAP (smaller binary) +cargo build -p miden-client-cli --no-default-features +``` + +## Quick Start + +### 1. Create an account + +With a running node: + +```bash +miden-client init +miden-client new-wallet +miden-client sync +``` + +Or without a node (requires `testing` feature): + +```bash +miden-client init +miden-client new-wallet --offline +``` + +### 2. Write a test script + +Create a file `test_debug.masm`: + +``` +begin + push.1.2 + add + push.3 + mul +end +``` + +### 3. Start the DAP server + +```bash +miden-client exec \ + --script-path test_debug.masm \ + --start-debug-adapter 127.0.0.1:4711 +``` + +The client will compile the script and wait for a DAP client to connect before executing. + +### 4. Connect a debugger + +In a separate terminal, connect the `miden-debug` TUI: + +```bash +miden-debug --dap-connect 127.0.0.1:4711 +``` + +You can now step through execution, inspect the stack, and set breakpoints. + + +## How it Works + +When `--start-debug-adapter` is passed: + +1. The client compiles the transaction script normally. +2. Instead of using the default `FastProcessor`, it creates a `DapExecutor` (from the `miden-debug` crate) which implements the `ProgramExecutor` trait. +3. The `DapExecutor` binds a TCP listener on the specified address and waits for a DAP client connection. +4. Once connected, the DAP client controls execution (continue, step, breakpoints, inspect state). +5. If the DAP client requests a restart, the client recompiles the script from disk and re-executes — enabling an edit-and-continue workflow. From d4506a5025205efea49c9728953a05866f717a25 Mon Sep 17 00:00:00 2001 From: djole Date: Mon, 13 Apr 2026 17:44:28 +0200 Subject: [PATCH 08/10] fixup: address comments --- bin/miden-cli/src/commands/new_account.rs | 17 +++++------------ crates/rust-client/src/transaction/mod.rs | 12 +++--------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index d2864fc533..18da65e3df 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -100,8 +100,8 @@ pub struct NewWalletCmd { pub deploy: bool, /// Seed local-only state so the wallet can be created and used for execution without a node. /// Only available when built with the `testing` feature. - #[cfg(feature = "testing")] - #[arg(long, default_value_t = false)] + #[cfg_attr(feature = "testing", arg(long, default_value_t = false))] + #[cfg_attr(not(feature = "testing"), arg(skip = false))] pub offline: bool, } @@ -131,10 +131,7 @@ impl NewWalletCmd { &package_paths, self.init_storage_data_path.clone(), self.deploy, - #[cfg(feature = "testing")] self.offline, - #[cfg(not(feature = "testing"))] - false, ) .await?; @@ -204,8 +201,8 @@ pub struct NewAccountCmd { pub deploy: bool, /// Seed local-only state so the account can be created and used for execution without a node. /// Only available when built with the `testing` feature. - #[cfg(feature = "testing")] - #[arg(long, default_value_t = false)] + #[cfg_attr(feature = "testing", arg(long, default_value_t = false))] + #[cfg_attr(not(feature = "testing"), arg(skip = false))] pub offline: bool, } @@ -223,10 +220,7 @@ impl NewAccountCmd { &self.packages, self.init_storage_data_path.clone(), self.deploy, - #[cfg(feature = "testing")] self.offline, - #[cfg(not(feature = "testing"))] - false, ) .await?; @@ -403,7 +397,7 @@ async fn create_client_account( package_paths: &[PathBuf], init_storage_data_path: Option, deploy: bool, - #[cfg_attr(not(feature = "testing"), allow(unused))] offline: bool, + offline: bool, ) -> Result { if package_paths.is_empty() { return Err(CliError::InvalidArgument(format!( @@ -412,7 +406,6 @@ async fn create_client_account( ", client_binary_name().display()))); } - #[cfg(feature = "testing")] if deploy && offline { return Err(CliError::InvalidArgument( "`--offline` cannot be combined with `--deploy`".to_string(), diff --git a/crates/rust-client/src/transaction/mod.rs b/crates/rust-client/src/transaction/mod.rs index 7219760268..060225c263 100644 --- a/crates/rust-client/src/transaction/mod.rs +++ b/crates/rust-client/src/transaction/mod.rs @@ -854,15 +854,9 @@ where TransactionExecutor<'store, 'auth, STORE, AUTH, miden_debug::DapExecutor>, TransactionExecutorError, > { - let mut executor = TransactionExecutor::new(data_store) - .with_program_executor::() - .with_options(self.exec_options)?; - if let Some(authenticator) = self.authenticator.as_deref() { - executor = executor.with_authenticator(authenticator); - } - executor = executor.with_source_manager(self.source_manager.clone()); - - Ok(executor) + Ok(self + .build_executor(data_store)? + .with_program_executor::()) } } From 4facba673459f147d2f5287d998a30fd0951f525 Mon Sep 17 00:00:00 2001 From: djole Date: Wed, 15 Apr 2026 09:14:22 +0200 Subject: [PATCH 09/10] fixup: address latest comments --- bin/miden-cli/src/commands/exec.rs | 13 +++-- bin/miden-cli/src/commands/new_account.rs | 18 +++--- crates/rust-client/src/sync/block_header.rs | 63 ++++----------------- 3 files changed, 28 insertions(+), 66 deletions(-) diff --git a/bin/miden-cli/src/commands/exec.rs b/bin/miden-cli/src/commands/exec.rs index e139f86129..c1db82f3af 100644 --- a/bin/miden-cli/src/commands/exec.rs +++ b/bin/miden-cli/src/commands/exec.rs @@ -1,4 +1,7 @@ use std::collections::BTreeMap; +use std::fs; +#[cfg(feature = "dap")] +use std::net::SocketAddr; use std::path::PathBuf; use clap::Parser; @@ -54,7 +57,7 @@ pub struct ExecCmd { /// and wait for a DAP client to connect before executing. #[cfg(feature = "dap")] #[arg(long = "start-debug-adapter")] - start_debug_adapter: Option, + start_debug_adapter: Option, } impl ExecCmd { @@ -70,7 +73,7 @@ impl ExecCmd { )); } - let program = std::fs::read_to_string(script_path)?; + let program = fs::read_to_string(script_path)?; let account_id = get_input_acc_id_by_prefix_or_default(&client, self.account_id.clone()).await?; @@ -85,7 +88,7 @@ impl ExecCmd { )); } - let input_data = std::fs::read_to_string(input_file)?; + let input_data = fs::read_to_string(input_file)?; deserialize_tx_inputs(&input_data)? }, None => vec![], @@ -150,7 +153,7 @@ impl ExecCmd { let script_path = PathBuf::from(&self.script_path); loop { - let program = std::fs::read_to_string(&script_path)?; + let program = fs::read_to_string(&script_path)?; let tx_script = client.code_builder().compile_tx_script(&program)?; let result = client @@ -164,7 +167,7 @@ impl ExecCmd { if config_handle.restart_requested() { config_handle.reset_restart(); - eprintln!("Recompiling from source and restarting debug session..."); + println!("Recompiling from source and restarting debug session..."); continue; } diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index 18da65e3df..c0df4d0102 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -100,7 +100,10 @@ pub struct NewWalletCmd { pub deploy: bool, /// Seed local-only state so the wallet can be created and used for execution without a node. /// Only available when built with the `testing` feature. - #[cfg_attr(feature = "testing", arg(long, default_value_t = false))] + #[cfg_attr( + feature = "testing", + arg(long, default_value_t = false, conflicts_with = "deploy") + )] #[cfg_attr(not(feature = "testing"), arg(skip = false))] pub offline: bool, } @@ -201,7 +204,10 @@ pub struct NewAccountCmd { pub deploy: bool, /// Seed local-only state so the account can be created and used for execution without a node. /// Only available when built with the `testing` feature. - #[cfg_attr(feature = "testing", arg(long, default_value_t = false))] + #[cfg_attr( + feature = "testing", + arg(long, default_value_t = false, conflicts_with = "deploy") + )] #[cfg_attr(not(feature = "testing"), arg(skip = false))] pub offline: bool, } @@ -406,12 +412,6 @@ async fn create_client_account( ", client_binary_name().display()))); } - if deploy && offline { - return Err(CliError::InvalidArgument( - "`--offline` cannot be combined with `--deploy`".to_string(), - )); - } - // Load the component templates and initialization storage data. let cli_config = CliConfig::load()?; @@ -474,6 +474,8 @@ async fn create_client_account( println!("Using custom authentication component from package (no key generated)."); } + let _ = offline; + #[cfg(feature = "testing")] if offline { client.prepare_offline_bootstrap().await?; diff --git a/crates/rust-client/src/sync/block_header.rs b/crates/rust-client/src/sync/block_header.rs index 9a6d60942f..9b53de22c5 100644 --- a/crates/rust-client/src/sync/block_header.rs +++ b/crates/rust-client/src/sync/block_header.rs @@ -5,17 +5,14 @@ use miden_protocol::Word; use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::crypto::merkle::MerklePath; use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, MmrPeaks, PartialMmr}; +#[cfg(feature = "testing")] +use miden_protocol::transaction::TransactionKernel; use tracing::warn; use crate::rpc::NodeRpcClient; use crate::store::{BlockRelevance, StoreError}; use crate::{Client, ClientError}; -/// Synthetic faucet account ID used only by the offline bootstrap genesis header so fee -/// parameters can reference a stable native-asset faucet without requiring node state. -#[cfg(feature = "testing")] -const OFFLINE_NATIVE_ASSET_FAUCET_ID: u128 = 0xab00_0000_0000_cd20_0000_ac00_0000_de00; - /// Network information management methods. impl Client { /// Retrieves a block header by its block number from the store. @@ -118,35 +115,7 @@ impl Client { #[cfg(feature = "testing")] fn synthetic_offline_genesis_header() -> BlockHeader { - use miden_protocol::account::AccountId; - use miden_protocol::block::account_tree::AccountTree; - use miden_protocol::block::nullifier_tree::NullifierTree; - use miden_protocol::block::{BlockNoteTree, Blockchain, FeeParameters}; - use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; - use miden_protocol::crypto::merkle::smt::Smt; - use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; - - let native_asset_id = AccountId::try_from(OFFLINE_NATIVE_ASSET_FAUCET_ID) - .expect("offline native asset faucet ID should be valid"); - let fee_parameters = - FeeParameters::new(native_asset_id, 500).expect("offline fee params should be valid"); - let validator_key = SecretKey::with_rng(&mut rand::rng()).public_key(); - let transactions = OrderedTransactionHeaders::new_unchecked(Vec::new()); - - BlockHeader::new( - 0, - Word::empty(), - BlockNumber::GENESIS, - Blockchain::new().commitment(), - AccountTree::::default().root(), - NullifierTree::::default().root(), - BlockNoteTree::empty().root(), - transactions.commitment(), - TransactionKernel.to_commitment(), - validator_key, - fee_parameters, - 0, - ) + BlockHeader::mock(BlockNumber::GENESIS, None, None, &[], TransactionKernel.to_commitment()) } // UTILS @@ -219,22 +188,17 @@ pub(crate) async fn fetch_block_header( #[cfg(test)] mod tests { - use alloc::vec::Vec; - use miden_protocol::block::account_tree::AccountTree; - use miden_protocol::block::nullifier_tree::NullifierTree; - use miden_protocol::block::{BlockHeader, BlockNoteTree, BlockNumber, Blockchain}; + use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::crypto::merkle::MerklePath; use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, Mmr, PartialMmr}; use miden_protocol::crypto::merkle::smt::Smt; - use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; + use miden_protocol::transaction::TransactionKernel; use miden_protocol::{Felt, Word}; - use super::{ - adjust_merkle_path_for_forest, - authenticated_block_nodes, - synthetic_offline_genesis_header, - }; + #[cfg(feature = "testing")] + use super::synthetic_offline_genesis_header; + use super::{adjust_merkle_path_for_forest, authenticated_block_nodes}; fn word(n: u64) -> Word { Word::new([Felt::new(n), Felt::new(0), Felt::new(0), Felt::new(0)]) @@ -355,19 +319,12 @@ mod tests { } #[test] - fn synthetic_offline_genesis_header_matches_empty_chain_state() { + #[cfg(feature = "testing")] + fn synthetic_offline_genesis_header_uses_mock_genesis() { let genesis = synthetic_offline_genesis_header(); assert_eq!(genesis.block_num(), BlockNumber::GENESIS); - assert_eq!(genesis.prev_block_commitment(), Word::empty()); - assert_eq!(genesis.chain_commitment(), Blockchain::new().commitment()); assert_eq!(genesis.account_root(), AccountTree::::default().root()); - assert_eq!(genesis.nullifier_root(), NullifierTree::::default().root()); - assert_eq!(genesis.note_root(), BlockNoteTree::empty().root()); - assert_eq!( - genesis.tx_commitment(), - OrderedTransactionHeaders::new_unchecked(Vec::new()).commitment() - ); assert_eq!(genesis.tx_kernel_commitment(), TransactionKernel.to_commitment()); } } From b2add88ae295ff0370d3d244c91987085f0c4c2e Mon Sep 17 00:00:00 2001 From: djole Date: Thu, 16 Apr 2026 09:28:04 +0200 Subject: [PATCH 10/10] docs: improve dap info --- docs/external/src/rust-client/debugging.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/external/src/rust-client/debugging.md b/docs/external/src/rust-client/debugging.md index 99c7580a5a..d8a1f411ff 100644 --- a/docs/external/src/rust-client/debugging.md +++ b/docs/external/src/rust-client/debugging.md @@ -29,6 +29,9 @@ cargo build -p miden-client-cli --features testing cargo build -p miden-client-cli --no-default-features ``` +If you build from source with default features disabled, include the `dap` feature to use +`--start-debug-adapter`. + ## Quick Start ### 1. Create an account