Skip to content

feat: enable DAP-backed transaction script debugging#1959

Merged
igamigo merged 10 commits into0xMiden:mainfrom
walnuthq:pr/enable-tx-debugging-v2
Apr 20, 2026
Merged

feat: enable DAP-backed transaction script debugging#1959
igamigo merged 10 commits into0xMiden:mainfrom
walnuthq:pr/enable-tx-debugging-v2

Conversation

@djolertrk
Copy link
Copy Markdown

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

@djolertrk
Copy link
Copy Markdown
Author

Original PR was #1883 (but the base branch was deleted) cc @juan518munoz @bitwalker

@djolertrk
Copy link
Copy Markdown
Author

djolertrk commented Apr 6, 2026

We need to publish new crates to miden-vm, then to update miden-debug not to use dependencies with specific SHA (to publish that) and then to update this PR to point to the newest crates.

Comment on lines +61 to +78
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(())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current state, offline seems like a purely testing feature, is this correct?
For example, what happens if I create a new account in the offline mode but then want to use this same client for other accounts? (IIUC, we will have written a synthetic genesis block commitment to the store, which will be incompatible once we connect to a real node)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is purely for local testing/debugging — it lets you create an account and execute programs without needing a running node. The synthetic genesis header is only meant to satisfy the client's requirement that a genesis block exists in the store.
If you then connect to a real node and sync, the genesis commitment mismatch would indeed be a problem. The expectation is that you'd use a fresh store (separate --store-path or rm the DB) when switching between offline and network modes. We could add a guard or warning for this in the future.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expectation is that you'd use a fresh store

This seems a bit fragile. It seems like a feature that should only be available in testing/debug mode. Are there any alternatives to adding an offline argument on this command?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. We could gate the --offline flag behind a cfg(feature = "testing"). Alternatively, we could skip the CLI flag entirely and only expose prepare_offline_bootstrap() as a library method for test harnesses — the DAP debugging workflow in the CLI could call it internally when --start-debug-adapter is used without an active node connection, rather than requiring the user to explicitly pass --offline at account creation time.

@djolertrk djolertrk force-pushed the pr/enable-tx-debugging-v2 branch from 8f5fa90 to 1c2072c Compare April 8, 2026 09:51
@djolertrk
Copy link
Copy Markdown
Author

Btw, we still need new miden-debug crates to be published (the rest of crates are published, such as vm and protocol, and we no longer rely on specific SHA).

@djolertrk djolertrk force-pushed the pr/enable-tx-debugging-v2 branch from 1c2072c to e9c2506 Compare April 8, 2026 12:48
@djolertrk
Copy link
Copy Markdown
Author

@juan518munoz I updated dependencies to use the newest crates from crates.io

@igamigo
Copy link
Copy Markdown
Collaborator

igamigo commented Apr 8, 2026

Hi @djolertrk! Client 0.14.0 was released yesterday. If we mean this to be patched into a 0.14.1 release, we should rebase this PR and change it to target the main branch. Sorry about the inconveniences. If this can wait until the 0.15 release, it should be fine to leave this PR as-is.

Comment thread bin/miden-cli/src/commands/exec.rs Outdated
///
/// If this binary was built without DAP support, using this flag returns an error.
#[arg(long = "start-debug-adapter")]
start_debug_adapter: Option<String>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use SocketAddr here, right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good call. Will switch to SocketAddr — clap can parse it directly.

Comment thread bin/miden-cli/src/commands/exec.rs Outdated
/// 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> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this is emulating some of what the MockRpcApi already does. Basically, you should be able to start a client with this RPC component (instead of the actual gRPC client), and you get a mockchain that you can manage yourself. This makes it so you don't have to manually mock objects - things like the genesis header will come automatically by calling client.sync_state() as long as the mockchain has been initialized and has at least 1 block.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. As I proposed above, for now, I've gated --offline and prepare_offline_bootstrap behind #[cfg(feature = "testing")] so it's not available in production builds. Switching to MockRpcApi would require restructuring how the CLI constructs the client (the RPC backend is set before commands run), so I'd prefer to do that as follow-up. Filed as a TODO.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread Cargo.toml Outdated
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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Comment on lines +137 to +143
async fn execute_program<AUTH: Keystore + Sync + 'static>(
&self,
client: &mut Client<AUTH>,
account_id: AccountId,
tx_script: TransactionScript,
advice_inputs: AdviceInputs,
) -> Result<[Felt; 16], CliError> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this compile well without the dap feature? Seems like it uses execute_program_with_dap

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. :)

@djolertrk
Copy link
Copy Markdown
Author

Hi @djolertrk! Client 0.14.0 was released yesterday. If we mean this to be patched into a 0.14.1 release, we should rebase this PR and change it to target the main branch. Sorry about the inconveniences. If this can wait until the 0.15 release, it should be fine to leave this PR as-is.

Good point, I will retarget this to main.

@djolertrk djolertrk force-pushed the pr/enable-tx-debugging-v2 branch from e9c2506 to dff01aa Compare April 8, 2026 15:15
@djolertrk djolertrk changed the base branch from next to main April 8, 2026 15:35
@djolertrk djolertrk force-pushed the pr/enable-tx-debugging-v2 branch from aee77f5 to 43a8bd8 Compare April 9, 2026 10:15
@djolertrk djolertrk requested a review from igamigo April 9, 2026 13:01
@djolertrk
Copy link
Copy Markdown
Author

djolertrk commented Apr 9, 2026

cc @juan518munoz - it is using crates from crates.io now.

This is the last piece to land for enabling the debugging of real transactions with the miden-debug.

Copy link
Copy Markdown
Contributor

@mmagician mmagician left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could you further document how to use this please? (including the two feature gates and what they do)

@djolertrk
Copy link
Copy Markdown
Author

Looks good, could you further document how to use this please? (including the two feature gates and what they do)

Thanks!
Sure. Do you mean introducing a docs/debugger-dap.md -- with all these scenarios explained?

@mmagician
Copy link
Copy Markdown
Contributor

Do you mean introducing a docs/debugger-dap.md -- with all these scenarios explained?

It would probably be good if it lived alongside all the other documentation we have (see how docs/ is structured)

@djolertrk
Copy link
Copy Markdown
Author

Do you mean introducing a docs/debugger-dap.md -- with all these scenarios explained?

It would probably be good if it lived alongside all the other documentation we have (see how docs/ is structured)

@mmagician addressed. Thanks!

@djolertrk
Copy link
Copy Markdown
Author

ping :)

Copy link
Copy Markdown
Collaborator

@igamigo igamigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I left some more small comments in order to see if we can simplify code and minimize branching/cfg conditionals. Would be nice if @mmagician (or anyone else) could take another look.

Comment thread bin/miden-cli/src/commands/new_account.rs Outdated
Comment thread crates/rust-client/src/transaction/mod.rs Outdated
@djolertrk
Copy link
Copy Markdown
Author

Thanks a lot for your comments!

Can someone merge this?

Comment on lines +98 to 105
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(())
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change proposed by make lint? Otherwise we could undo it so we keep the diff as short as possible.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. This was not needed for lint.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has many std imports that could be top-leveled.

Comment thread bin/miden-cli/src/commands/exec.rs Outdated

if config_handle.restart_requested() {
config_handle.reset_restart();
eprintln!("Recompiling from source and restarting debug session...");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be considered an error?

Suggested change
eprintln!("Recompiling from source and restarting debug session...");
println!("Recompiling from source and restarting debug session...");

"`--offline` cannot be combined with `--deploy`".to_string(),
));
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use claps conflict_with? For example

#[arg(long, default_value_t = false, conflicts_with = "init_storage_data_path")]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch.

Comment on lines +121 to +127
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};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move this upwards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of them are gone now.

validator_key,
fee_parameters,
0,
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason as to not using BlockHeader::mock(BlockNumber::GENESIS, None, None, &[], TransactionKernel.to_commitment())?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, using BlockHeader::mock(...) is cleaner here. Updated to use it.


```bash
# Default build (DAP enabled)
cargo build -p miden-client-cli
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any significant overhead building with dap enabled? If so it'd be preferable to leave it as a non-default feature.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d prefer to keep it enabled by default. The DAP support is just pulled from miden-debug, and it does not affect normal execution unless the user explicitly passes --start-debug-adapter. Keeping it default makes this important debugging path available out of the box, while users who want a smaller build can still opt out with --no-default-features.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense for it to be a default feature, since as @djolertrk pointed out, it will mean that clients are able to be used for debugging out of the box. But so long as the client installed by midenup has the feature enabled, then it isn't as important whether the feature is actually in the default set or not (though I think it would be important to document for users building from source that the feature is needed to support debugging).

Copy link
Copy Markdown
Author

@djolertrk djolertrk Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added one more sentence into the docs to clarify this. Thanks @bitwalker!

Comment on lines +55 to +57
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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this can be replaced with a call to ensure_rpc_limits_in_place

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly. ensure_rpc_limits_in_place() fetches limits from the node if they are not already cached, while prepare_offline_bootstrap() must work without a node. Here we intentionally seed limits from the store or RpcLimits::default() and then cache them in both the store and RPC client.

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`
…efault

- 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
Replace local path dependency with published crate.
- 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
@djolertrk djolertrk force-pushed the pr/enable-tx-debugging-v2 branch from 5ed3667 to b2add88 Compare April 16, 2026 07:40
@djolertrk
Copy link
Copy Markdown
Author

someone needs to approve the CI. :)

@bitwalker
Copy link
Copy Markdown
Collaborator

@igamigo @juan518munoz Looks to me like this is ready to merge - are there any remaining obstacles?

@igamigo
Copy link
Copy Markdown
Collaborator

igamigo commented Apr 17, 2026

@igamigo @juan518munoz Looks to me like this is ready to merge - are there any remaining obstacles?

I was hoping to get one final pass from @mmagician (pinged him separately and he told me he'd take a look), but otherwise we should be good to proceed.

@djolertrk
Copy link
Copy Markdown
Author

ping :)

@igamigo igamigo merged commit 9fd682a into 0xMiden:main Apr 20, 2026
33 checks passed
@igamigo
Copy link
Copy Markdown
Collaborator

igamigo commented Apr 20, 2026

Merging this for now. If @mmagician or anyone has any further comments, we can open an issue to discuss.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants