| name | local-node-validation |
| description | Validates Miden contracts against a local node. Covers node setup, Rust binary adaptation, state verification, and troubleshooting. Use after MockChain tests pass to verify contracts work against a real node. |
Local Node Validation
Validates that contracts working in MockChain also work against a real Miden node. This catches known MockChain/live-node behavior gaps before they become harder to debug in production or the frontend.
Why This Matters
MockChain simplifies execution in ways that hide real-world failures:
- No automatic block production -- MockChain requires explicit
prove_next_block(). A live node produces blocks on its own schedule.
- No network transport -- MockChain does not simulate the network transaction builder (ntx-builder) that handles network notes.
- No RPC latency or timeouts -- MockChain executes locally and instantly. Live nodes have gRPC round-trips with configurable timeouts.
- No version/genesis validation -- MockChain skips the protocol-version check that a live node negotiates at connect (a mismatched node is rejected).
- Account update block numbers not tracked -- MockChain returns chain tip instead of actual update block number.
- No mempool or batching -- MockChain does not simulate transaction queuing, batch formation, or block inclusion delays.
Prerequisites
The local-node launch CLI lives in the 0xMiden/node repo, not in miden-client. The commands below are the topology the client's make start-node target (scripts/start-test-node.sh) drives; confirm exact flags against your installed node's --help for the version you run.
Step 1: Clean State and Start Local Node
Every node session must start from clean state. Stale store files and keystore directories cause conflicts, deserialization errors, and misleading test results. Always wipe before starting. (v0.15 artifacts also do not round-trip across versions, so a fresh store is required after any version change.)
The simplest path is the client's make start-node target, which runs the bundled scripts/start-test-node.sh helper: it installs the node binaries (pinned to your Cargo.lock), generates genesis, bootstraps each component, and starts the split topology for you:
make start-node
make start-node-background
This brings up the four-component topology and exposes the RPC on 127.0.0.1:57291 (the client default, MIDEN_NODE_PORT).
If you run the node binaries directly instead of via make start-node, the shape is below. Treat it as a reference skeleton, not a copy-paste recipe: it omits details the script handles for you (it does not show generating the genesis config the validator bootstraps from, and it leaves out the shared network-tx auth header that the sequencer and ntx-builder must agree on or the sequencer rejects the ntx-builder's transactions). Verify every subcommand and flag against --help for your node version, or just use make start-node.
miden-validator bootstrap --data-directory <data>/validator \
--genesis-block-directory <data>/genesis --accounts-directory <data>/accounts \
--genesis-config-file <data>/genesis-config/genesis.toml
miden-node bootstrap --data-directory <data>/node --file <data>/genesis/genesis.dat
miden-ntx-builder bootstrap --data-directory <data>/ntx-builder --file <data>/genesis/genesis.dat
miden-validator start --listen 127.0.0.1:50101 --data-directory <data>/validator
miden-node sequencer --rpc.listen 127.0.0.1:57291 --data-directory <data>/node \
--validator.url http://127.0.0.1:50101 --ntx-builder.url http://127.0.0.1:50301 \
--block.interval 3s --batch.interval 1s
miden-remote-prover --kind=transaction --port=50051
miden-ntx-builder start --listen 127.0.0.1:50301 --rpc.url http://127.0.0.1:57291 \
--tx-prover.url http://127.0.0.1:50051 --data-directory <data>/ntx-builder
This clean-start sequence is mandatory every time. Do not attempt to reuse state from a previous session.
Step 2: Add a localhost client helper
Add a setup_local_client() function to whichever helper module in your integration / test harness already hosts the network setup_client() equivalent. Name and location are up to you -- adjust to your repo's layout.
.sqlite_store(..) is not an inherent ClientBuilder method in v0.15 -- it comes from an extension trait in the miden-client-sqlite-store crate. You must bring it into scope or the call fails to compile (method not found):
use miden_client_sqlite_store::ClientBuilderSqliteExt;
pub async fn setup_local_client() -> Result<ClientSetup> {
let endpoint = Endpoint::new("http".into(), "localhost".into(), Some(57291));
let timeout_ms = 10_000;
let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
let keystore_path = std::path::PathBuf::from("../local-keystore");
let keystore = Arc::new(FilesystemKeyStore::new(keystore_path)
.context("Failed to initialize local keystore")?);
let store_path = std::path::PathBuf::from("../local-store.sqlite3");
let client = ClientBuilder::new()
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
.in_debug_mode(true.into())
.build()
.await
.context("Failed to build local Miden client")?;
Ok(ClientSetup { client, keystore })
}
Use separate paths (local-keystore/, local-store.sqlite3) to avoid contaminating testnet state.
Step 3: Create a local validation binary
Add a local validation binary alongside your existing network/testnet validation binary, mirroring its structure but swapping in setup_local_client(). Pick any conventional name for it (for example validate_local) -- adjust to your repo's binary layout.
The binary must:
- Call
setup_local_client() instead of the network setup function
- Sync state:
client.sync_state().await?
- Build contracts (same as the existing binary)
- Create accounts, create notes, submit transactions
- Sync again after each transaction submission
- Wait for transaction inclusion (poll
sync_state until account state updates)
- Verify final state matches MockChain test expectations
- Print clear pass/fail for each verification step
Key differences from the network binary:
- Localhost endpoint (port 57291)
- Separate keystore and store paths
- Must handle block production timing (sync + wait between submissions)
Step 4: Run and Verify
Ensure clean client state before running (the node should already be clean from Step 1):
rm -rf local-keystore/ local-store.sqlite3
cargo run --bin <your-local-validation-binary> --release
Verification Checklist
Step 5: Inspect Node Logs
Run the node with verbose logging. The helper script honors RUST_LOG and writes a per-component log file per service; if you launch the binaries directly, set it on the process you want to inspect (the sequencer carries the RPC):
RUST_LOG=info make start-node
RUST_LOG=info miden-node sequencer --rpc.listen 127.0.0.1:57291 --data-directory <data>/node \
--validator.url http://127.0.0.1:50101 --ntx-builder.url http://127.0.0.1:50301 \
--block.interval 3s --batch.interval 1s
Look for:
- Transaction acceptance/rejection messages
- Block production confirmations
- Error or warning lines
Troubleshooting
| Symptom | Cause | Fix |
|---|
Unavailable RPC error | Node not running or wrong port | Start node, verify the sequencer's RPC is listening on 57291 |
| Version mismatch error | Node and client crate versions differ | Run a v0.15 node built from the node source pinned in your client's Cargo.lock; the protocol version is negotiated at connect and a mismatch is rejected |
| Transaction rejected | Invalid proof or state | Check contract code, reset node data, try again |
| Account not found after creation | Haven't synced | Call sync_state() after account creation |
| Store errors or deserialization failures | Stale state from previous session (or artifacts from an earlier protocol version, which do not round-trip) | Wipe the node data, keystore, and client store, then re-bootstrap from a fresh genesis |
.sqlite_store(..) does not compile | Extension trait not in scope | use miden_client_sqlite_store::ClientBuilderSqliteExt; |
| Block not produced | Node produces blocks on the sequencer's configured cadence | Submit a transaction; check the sequencer's --block.interval (and --batch.interval) settings, or consult miden-node sequencer --help |