Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
49248b2
feat: trace relevant http headers (#1982)
Mirko-von-Leipzig Apr 22, 2026
e08c476
feat: inject basic trace fields for requests (#1983)
Mirko-von-Leipzig Apr 22, 2026
7ef15da
refactor(store): get account vault details from account state forest …
kkovaacs Apr 24, 2026
8a73b9f
feat(rpc): cache reference block commitments (#1996)
Mirko-von-Leipzig Apr 24, 2026
9fe7ec4
fix: mempool occasional panics when store is ahead by a block (#1984)
Mirko-von-Leipzig Apr 24, 2026
29a7c34
feat(mempool): Avoid allocating in `SelectedBatch` query (#1991)
Mirko-von-Leipzig Apr 24, 2026
046a728
ci: cache rust builds on `main` (#2000)
Mirko-von-Leipzig Apr 24, 2026
4477568
fix(github): remove prover proxy job from debian publish workflow (#2…
kkovaacs Apr 24, 2026
4ac37ea
fix(docker): use correct runtime-base image (#1999)
kkovaacs Apr 24, 2026
a44d980
perf(db): disable SQLite memory accounting (#1988)
kkovaacs Apr 24, 2026
7d4c33f
ci: optimise for test runtime execution (#2004)
Mirko-von-Leipzig Apr 28, 2026
dd6556a
ci: fix profile overrides not observed by cache (#2009)
Mirko-von-Leipzig Apr 28, 2026
d3c030e
chore: Split apply_block and improve instrumentation (#1896)
sergerad Apr 28, 2026
bb74d9d
refactor(lru_cache): simplify locking in LRU cache wrapper (#2015)
kkovaacs Apr 29, 2026
0364fc3
ci: simplify caching (#2011)
Mirko-von-Leipzig Apr 29, 2026
b927515
chore: increment crate versions to v0.14.10
bobbinth Apr 30, 2026
1468f8e
refactor(store): pull storage map entries from account state forest (…
kkovaacs Apr 30, 2026
255d863
refactor(ntx-builder): use sync mutexes where possible (#2021)
kkovaacs May 4, 2026
513660b
ci: add shear, workspace inheritance, and zizmor checks, and move hea…
huitseeker Apr 30, 2026
b491987
ci: install toolchain properly (#2026)
Mirko-von-Leipzig May 4, 2026
a5c3f3e
ci: cache each job (#2025)
Mirko-von-Leipzig May 4, 2026
97c9324
feat(store): use RocksDB-backed persistent account state forest (#2020)
kkovaacs May 4, 2026
705f23f
Merge branch 'main' into mirko/port-ci-main
Mirko-von-Leipzig May 4, 2026
956132c
Cherrypick `ci` improvements from `next` #2040
Mirko-von-Leipzig May 4, 2026
41c2aca
chore(telemetry): `client.address` should be the resolved client addr…
Mirko-von-Leipzig May 4, 2026
fba447a
feat(store): analyse database on disk size via filesystem instead of …
bigeez May 5, 2026
b3fb939
chore: bump dependencies (#2055)
Mirko-von-Leipzig May 6, 2026
8f1ed50
fix: replace blocking-in-async (#2041)
Ollie202 May 6, 2026
f1e1448
feat: remove `BlockSigner` trait (#2057)
Mirko-von-Leipzig May 7, 2026
0591fbd
Merge main into next
Mirko-von-Leipzig May 7, 2026
ce77ca5
fix: correctly propagate the current span to blocking tasks (#2061)
kkovaacs May 7, 2026
f402b95
Pull in spawn_blocking instrumentation
Mirko-von-Leipzig May 7, 2026
49f6ca7
Merge branch 'next' into mirko/merge-maoin
Mirko-von-Leipzig May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
- Added `StoreReplica` gRPC service with endpoints for streaming blocks and proofs ([#1987](https://github.com/0xMiden/node/pull/1987)).
- 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)).
- 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)).
- Implemented persistent RocksDB backend for `AccountStateForest`, improving startup time ([#2020](https://github.com/0xMiden/node/pull/2020)).
- [BREAKING] `BlockRange.block_to` is now required for all RPC endpoints ([#2056](https://github.com/0xMiden/node/pull/2056)).

## v0.14.10 (2026-05-29)
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions bin/genesis/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,17 +205,17 @@ mod tests {

/// Parses the generated genesis.toml, builds a genesis block, and asserts the bridge account
/// is included with nonce=1.
async fn assert_valid_genesis_block(dir: &Path) {
fn assert_valid_genesis_block(dir: &Path) {
let bridge_id = AccountFile::read(dir.join("bridge.mac")).unwrap().account.id();

let config = GenesisConfig::read_toml_file(&dir.join("genesis.toml")).unwrap();
let signer = SecretKey::read_from_bytes(&[0x01; 32]).unwrap();
let (state, _) = config.into_state(signer).unwrap();
let (state, _) = config.into_state(signer.public_key()).unwrap();

let bridge = state.accounts.iter().find(|a| a.id() == bridge_id).unwrap();
assert_eq!(bridge.nonce(), ONE);

state.into_block().await.expect("genesis block should build");
state.into_block(&signer).expect("genesis block should build");
}

#[tokio::test]
Expand All @@ -229,7 +229,7 @@ mod tests {
let ger = AccountFile::read(dir.path().join("ger_manager.mac")).unwrap();
assert_eq!(ger.auth_secret_keys.len(), 1);

assert_valid_genesis_block(dir.path()).await;
assert_valid_genesis_block(dir.path());
}

#[tokio::test]
Expand All @@ -249,6 +249,6 @@ mod tests {
let ger = AccountFile::read(dir.path().join("ger_manager.mac")).unwrap();
assert!(ger.auth_secret_keys.is_empty());

assert_valid_genesis_block(dir.path()).await;
assert_valid_genesis_block(dir.path());
}
}
47 changes: 20 additions & 27 deletions bin/node/src/commands/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig};
use miden_node_utils::clap::GrpcOptionsInternal;
use miden_node_utils::fs::ensure_empty_directory;
use miden_node_utils::grpc::UrlExt;
use miden_node_utils::signer::BlockSigner;
use miden_node_validator::{Validator, ValidatorSigner};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey;
use miden_protocol::utils::serde::{Deserializable, Serializable};
Expand Down Expand Up @@ -195,42 +194,28 @@ impl ValidatorCommand {

// Bootstrap with KMS key or local key.
let signer = validator_key.into_signer().await?;
match signer {
ValidatorSigner::Kms(signer) => {
build_and_write_genesis(
config,
signer,
accounts_directory,
genesis_block_directory,
data_directory,
)
.await
},
ValidatorSigner::Local(signer) => {
build_and_write_genesis(
config,
signer,
accounts_directory,
genesis_block_directory,
data_directory,
)
.await
},
}
build_and_write_genesis(
config,
signer,
accounts_directory,
genesis_block_directory,
data_directory,
)
.await
}
}

/// Builds the genesis state, writes account secret files, signs the genesis block, writes it
/// to disk, and initializes the validator's database with the genesis block as the chain tip.
async fn build_and_write_genesis(
config: GenesisConfig,
signer: impl BlockSigner,
signer: ValidatorSigner,
accounts_directory: &Path,
genesis_block_directory: &Path,
data_directory: &Path,
) -> anyhow::Result<()> {
// Build genesis state with the provided signer.
let (genesis_state, secrets) = config.into_state(signer)?;
let (genesis_state, secrets) = config.into_state(signer.public_key())?;

// Write account secret files.
for item in secrets.as_account_files(&genesis_state) {
Expand All @@ -246,8 +231,16 @@ async fn build_and_write_genesis(
}

// Build the signed genesis block.
let genesis_block =
genesis_state.into_block().await.context("failed to build the genesis block")?;
let unsigned_genesis_block = genesis_state
.into_unsigned_block()
.context("failed to build the unsigned genesis block")?;
let signature = signer
.sign(unsigned_genesis_block.header())
.await
.context("failed to sign the genesis block")?;
let genesis_block = unsigned_genesis_block
.into_block(signature)
.context("failed to build the genesis block")?;

// Serialize and write the genesis block to disk.
let block_bytes = genesis_block.inner().to_bytes();
Expand Down
23 changes: 19 additions & 4 deletions bin/remote-prover/src/server/prover.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use miden_block_prover::LocalBlockProver;
use miden_node_proto::BlockProofRequest;
use miden_node_utils::ErrorReport;
use miden_node_utils::spawn::spawn_blocking_in_current_span;
use miden_node_utils::tracing::OpenTelemetrySpanExt;
use miden_protocol::MIN_PROOF_SECURITY_LEVEL;
use miden_protocol::batch::{ProposedBatch, ProvenBatch};
Expand Down Expand Up @@ -112,8 +113,15 @@ impl ProveRequest for LocalBatchProver {
type Output = ProvenBatch;

async fn prove(&self, input: Self::Input) -> Result<Self::Output, tonic::Status> {
self.prove(input)
.map_err(|e| tonic::Status::internal(e.as_report_context("failed to prove batch")))
let prover = self.clone();

spawn_blocking_in_current_span(move || {
prover
.prove(input)
.map_err(|e| tonic::Status::internal(e.as_report_context("failed to prove batch")))
})
.await
.map_err(|e| tonic::Status::internal(e.as_report_context("batch prover task panicked")))?
}
}

Expand All @@ -123,8 +131,15 @@ impl ProveRequest for LocalBlockProver {
type Output = BlockProof;

async fn prove(&self, input: Self::Input) -> Result<Self::Output, tonic::Status> {
let prover = self.clone();
let BlockProofRequest { tx_batches, block_header, block_inputs } = input;
self.prove(tx_batches, &block_header, block_inputs)
.map_err(|e| tonic::Status::internal(e.as_report_context("failed to prove block")))

spawn_blocking_in_current_span(move || {
prover
.prove(tx_batches, &block_header, block_inputs)
.map_err(|e| tonic::Status::internal(e.as_report_context("failed to prove block")))
})
.await
.map_err(|e| tonic::Status::internal(e.as_report_context("block prover task panicked")))?
}
}
131 changes: 90 additions & 41 deletions bin/stress-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,42 @@ After building the binary, you can run the following command to generate one mil

The store file will then be located at `./data/miden-store.sqlite3`.

The seed data can be tuned for account-detail benchmarks:

- `--public-accounts-percentage` controls how many generated accounts are public. The default is `0`.
- `--storage-map-entries` adds a deterministic storage map with the given number of entries to every public account. The default is `0`.
- `--vault-entries` adds the given number of distinct fungible assets to every public account's vault. The default is `1`, and the value must fit within the protocol note asset limit.
- `--account-update-blocks` appends the given number of blocks after account initialization. These blocks randomly update existing accounts and rotate updates through the seeded storage-map entries. The default is `0`.

For example, this creates public accounts with storage maps, multiple vault assets, and additional account-update history:

```bash
miden-node-stress-test seed-store \
--data-directory ./data \
--num-accounts 100000 \
--public-accounts-percentage 50 \
--storage-map-entries 128 \
--vault-entries 5 \
--account-update-blocks 100
```

## Benchmark Store

This command allows to run stress tests against the Store component. These tests use the dump file with accounts ids created when seeding the store, so be sure to run the `seed-store` command beforehand.

The endpoints that you can test are:
- `load_state`
- `sync_notes`
- `sync_nullifiers`
- `sync_transactions`
- `load-state`
- `get-account`
- `sync-notes`
- `sync-nullifiers`
- `sync-transactions`
- `sync-chain-mmr`

Most benchmarks accept options to control the number of iterations and concurrency level. The `load_state` endpoint is different - it simply measures the one-time startup cost of loading the state from disk.
Most benchmarks accept options to control the number of iterations and concurrency level. The `load-state` endpoint is different - it simply measures the one-time startup cost of loading the state from disk.

The `get-account` benchmark uses the account id dump created by `seed-store`, selects public accounts, and requests account details from the store. Each request asks for vault details and all entries from a storage map slot. By default, it uses the slot created by `--storage-map-entries`: `miden::mock::stress_test::map`. You can request a different slot with `--storage-map-slot`.

**Note on Concurrency**: For the endpoints that support it (`sync_notes`, `sync_nullifiers`), the concurrency parameter controls how many requests are sent in parallel to the store. Since these benchmarks run against a local store (no network overhead), higher concurrency values can help identify bottlenecks in the store's internal processing. The latency measurements exclude network time and represent pure store processing time.
**Note on Concurrency**: For request benchmarks, the concurrency parameter controls how many requests are sent in parallel to the store. Since these benchmarks run against a local store (no network overhead), higher concurrency values can help identify bottlenecks in the store's internal processing. The latency measurements exclude network time and represent pure store processing time.

Example usage:

Expand All @@ -39,6 +61,16 @@ miden-node-stress-test benchmark-store \
sync-notes
```

To benchmark public account detail loading, seed public accounts first and then run:

```bash
miden-node-stress-test benchmark-store \
--data-directory ./data \
--iterations 10000 \
--concurrency 16 \
get-account
```

### Results

The following results were obtained using a store with 100k accounts, half of which are public.
Expand All @@ -58,49 +90,49 @@ Average DB growth rate: 325.3 KB per block

> Note: Each block contains 256 transactions (16 batches * 16 transactions).

| Block | Insert Time (ms) | Get Block Inputs Time (ms) | Get Batch Inputs Time (ms) | Block Size (KB) | DB Size (MB) |
| ------ | ------------------ | ----------------------------- | ------------------------------ | ------------------ | ------------- |
| 0 | 22 | 1 | 0 | 375.6 | 0.3 |
| 50 | 186 | 9 | 1 | 473.6 | 22.2 |
| 100 | 199 | 10 | 1 | 473.6 | 40.7 |
| 150 | 219 | 10 | 1 | 473.6 | 58.1 |
| 200 | 218 | 11 | 1 | 473.6 | 74.8 |
| 250 | 222 | 11 | 1 | 473.6 | 91.6 |
| 300 | 228 | 12 | 1 | 473.6 | 108.1 |
| 350 | 232 | 13 | 1 | 473.6 | 124.4 |
| Block | Insert Time (ms) | Get Block Inputs Time (ms) | Get Batch Inputs Time (ms) | Block Size (KB) | DB Size (MB) |
| ----- | ---------------- | -------------------------- | -------------------------- | --------------- | ------------ |
| 0 | 22 | 1 | 0 | 375.6 | 0.3 |
| 50 | 186 | 9 | 1 | 473.6 | 22.2 |
| 100 | 199 | 10 | 1 | 473.6 | 40.7 |
| 150 | 219 | 10 | 1 | 473.6 | 58.1 |
| 200 | 218 | 11 | 1 | 473.6 | 74.8 |
| 250 | 222 | 11 | 1 | 473.6 | 91.6 |
| 300 | 228 | 12 | 1 | 473.6 | 108.1 |
| 350 | 232 | 13 | 1 | 473.6 | 124.4 |

#### Database stats

> Note: Database contains 100215 accounts and 100215 notes across all blocks.

| Table | Size (MB) | KB/Entry |
| ---------------------------------- | --------------- | ---------- |
| accounts | 26.1 | 0.3 |
| account_deltas | 1.2 | 0.0 |
| account_fungible_asset_deltas | 2.2 | 0.0 |
| account_non_fungible_asset_updates | 0.0 | - |
| account_storage_map_updates | 0.0 | - |
| account_storage_slot_updates | 3.1 | 0.1 |
| block_headers | 0.1 | 0.3 |
| notes | 49.1 | 0.5 |
| note_scripts | 0.0 | 8.0 |
| nullifiers | 4.6 | 0.0 |
| transactions | 6.0 | 0.1 |
| Table | Size (MB) | KB/Entry |
| ---------------------------------- | --------- | -------- |
| accounts | 26.1 | 0.3 |
| account_deltas | 1.2 | 0.0 |
| account_fungible_asset_deltas | 2.2 | 0.0 |
| account_non_fungible_asset_updates | 0.0 | - |
| account_storage_map_updates | 0.0 | - |
| account_storage_slot_updates | 3.1 | 0.1 |
| block_headers | 0.1 | 0.3 |
| notes | 49.1 | 0.5 |
| note_scripts | 0.0 | 8.0 |
| nullifiers | 4.6 | 0.0 |
| transactions | 6.0 | 0.1 |

#### Index stats

| Index | Size (MB) |
| ---------------------------------- | --------------- |
| idx_accounts_network_prefix | 0.0 |
| idx_notes_note_id | 4.4 |
| idx_notes_sender | 2.9 |
| idx_notes_tag | 1.6 |
| idx_notes_nullifier | 4.4 |
| idx_unconsumed_network_notes | 1.1 |
| idx_nullifiers_prefix | 4.3 |
| idx_nullifiers_block_num | 4.2 |
| idx_transactions_account_id | 5.6 |
| idx_transactions_block_num | 4.2 |
| Index | Size (MB) |
| ---------------------------- | --------- |
| idx_accounts_network_prefix | 0.0 |
| idx_notes_note_id | 4.4 |
| idx_notes_sender | 2.9 |
| idx_notes_tag | 1.6 |
| idx_notes_nullifier | 4.4 |
| idx_unconsumed_network_notes | 1.1 |
| idx_nullifiers_prefix | 4.3 |
| idx_nullifiers_block_num | 4.2 |
| idx_transactions_account_id | 5.6 |
| idx_transactions_block_num | 4.2 |


Current results of the store stress-tests:
Expand Down Expand Up @@ -175,5 +207,22 @@ Pagination statistics:
Average pages per run: 1.00
```

- get-account
``` bash
$ miden-node-stress-test benchmark-store --data-directory ./data --iterations 10000 --concurrency 16 get-account

Average request latency: 937.969µs
P50 request latency: 688.332µs
P95 request latency: 932.549µs
P99 request latency: 1.119977ms
P99.9 request latency: 42.992839ms
GetAccount statistics:
Total runs: 10000
Storage map limit exceeded responses: 0
Average returned storage map entries: 64.00
Vault limit exceeded responses: 0
Average returned vault assets: 2.00
```

## License
This project is [MIT licensed](../../LICENSE).
Loading
Loading