Skip to content
Open
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
9ed9c7a
Initial validator db impl
sergerad Jan 29, 2026
560c79a
Add basic validation logic
sergerad Jan 29, 2026
77b1616
Simplify schema
sergerad Jan 29, 2026
0636937
Changelog, toml
sergerad Jan 29, 2026
6f4b0d3
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Jan 29, 2026
71c90b8
Fix comment
sergerad Jan 29, 2026
7bdd615
Add error case
sergerad Jan 29, 2026
4e76c2e
Fix standalone validator setup alongside bundled components
sergerad Jan 30, 2026
c01448e
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 1, 2026
2b8008f
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 2, 2026
4fe9890
RM row affected check
sergerad Feb 2, 2026
f3aaeba
Fix comment
sergerad Feb 2, 2026
405737e
Add UnvalidatedTransactions err
sergerad Feb 2, 2026
2d7a3ba
Rename insecure key
sergerad Feb 2, 2026
d3c8770
Move to kv store
sergerad Feb 3, 2026
1ec03ef
Misc cleanup
sergerad Feb 3, 2026
4735bd1
Undo pub changes to store crate
sergerad Feb 3, 2026
147b0aa
Changelog
sergerad Feb 3, 2026
1596c50
Missing file
sergerad Feb 3, 2026
92b16f5
Update dockerfile
sergerad Feb 3, 2026
411c8e8
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 3, 2026
ae5510c
Fix test
sergerad Feb 3, 2026
15b9899
Revert "Fix test"
sergerad Feb 3, 2026
bed51da
Revert "Merge branch 'next' of github.com:0xMiden/miden-node into ser…
sergerad Feb 3, 2026
57b1671
Revert "ci(docker): use `cargo chef` and cache to github (#1631)"
sergerad Feb 3, 2026
4d1baee
Revert "Update dockerfile"
sergerad Feb 3, 2026
6230ce8
Revert "Missing file"
sergerad Feb 3, 2026
e75e486
Revert "Changelog"
sergerad Feb 3, 2026
2de6f00
Revert "Undo pub changes to store crate"
sergerad Feb 3, 2026
189ad4b
Revert "Misc cleanup"
sergerad Feb 3, 2026
62d0cd2
Revert "Move to kv store"
sergerad Feb 3, 2026
74aa074
Add find_unvalidated_ fn
sergerad Feb 3, 2026
2db6985
Refactor for tx summary
sergerad Feb 3, 2026
41e27b5
Impl todo for tx summary
sergerad Feb 3, 2026
06bb151
Revert workflow
sergerad Feb 3, 2026
5d76a80
Fix var name
sergerad Feb 3, 2026
a468514
Revert dockerfile
sergerad Feb 3, 2026
c0c692b
Fix merge issues
sergerad Feb 4, 2026
2e92e72
Fix more merge issues
sergerad Feb 4, 2026
88624e5
More merge issues
sergerad Feb 4, 2026
60e1e1f
Another merge issue
sergerad Feb 4, 2026
81db1de
Fix comment
sergerad Feb 4, 2026
59858c7
Fix unvalidated logic
sergerad Feb 4, 2026
37eab16
Add missing build.rs
sergerad Feb 4, 2026
4fcc6c8
machete
sergerad Feb 4, 2026
072dbe3
Use tempdir
sergerad Feb 8, 2026
3073aa4
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 8, 2026
e639c97
UnvalidatedTransactions wording
sergerad Feb 8, 2026
cff7f1b
err as report
sergerad Feb 8, 2026
4cb0459
Rm order query
sergerad Feb 8, 2026
6885616
RM deadcode
sergerad Feb 8, 2026
e612a98
Align sql add comments
sergerad Feb 8, 2026
624c581
Add raw sql comment
sergerad Feb 8, 2026
2289a42
Fix validator key var name
sergerad Feb 8, 2026
78030a7
Added explanatory comment for store validator key arg
sergerad Feb 8, 2026
425f645
ValidatorConfig::to_addresses()
sergerad Feb 8, 2026
025cc93
Rm pub
sergerad Feb 8, 2026
6fa8f46
Add ValidatedTransactionInfo
sergerad Feb 8, 2026
3af9c0a
Missing file
sergerad Feb 8, 2026
a6bf55d
Fix comment
sergerad Feb 8, 2026
a7a2455
Fix var name
sergerad Feb 8, 2026
e60ab9b
Fix changelog
sergerad Feb 9, 2026
28ca16f
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 10, 2026
adb60b3
Toml
sergerad Feb 10, 2026
00e45be
Revert dockerfile
sergerad Feb 10, 2026
5ca7001
Add block_num col and index + ValidatedTransactionInfoBlob
sergerad Feb 10, 2026
0081637
Fix comment
sergerad Feb 11, 2026
c341458
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 11, 2026
f3d1cfd
Update changelog wording
sergerad Feb 12, 2026
7937a7f
Refactor validated tx info
sergerad Feb 12, 2026
65b5af0
Source errors
sergerad Feb 12, 2026
61a9548
Missing file
sergerad Feb 12, 2026
d032732
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-va…
sergerad Feb 12, 2026
806ec05
Instrument for errors, on conflict do nothing, WAL and timeout
sergerad Feb 12, 2026
959f7e0
Instrument db fns
sergerad Feb 12, 2026
528a265
RM unnecessary src
sergerad Feb 13, 2026
4f46477
Add 5s pragma
sergerad Feb 13, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [BREAKING] Move block proving from Blocker Producer to the Store ([#1579](https://github.com/0xMiden/miden-node/pull/1579)).
- [BREAKING] Updated miden-base dependencies to use `next` branch; renamed `NoteInputs` to `NoteStorage`, `.inputs()` to `.storage()`, and database `inputs` column to `storage` ([#1595](https://github.com/0xMiden/miden-node/pull/1595)).
- Validator now persists validated transactions ([#1614](https://github.com/0xMiden/miden-node/pull/1614)).

### Changes

Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion bin/node/.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ MIDEN_NODE_STORE_RPC_URL=
MIDEN_NODE_STORE_NTX_BUILDER_URL=
MIDEN_NODE_STORE_BLOCK_PRODUCER_URL=
MIDEN_NODE_VALIDATOR_BLOCK_PRODUCER_URL=
MIDEN_NODE_VALIDATOR_INSECURE_SECRET_KEY=
MIDEN_NODE_VALIDATOR_KEY=
MIDEN_NODE_RPC_URL=http://0.0.0.0:57291
MIDEN_NODE_DATA_DIRECTORY=./
MIDEN_NODE_ENABLE_OTEL=true
Expand Down
189 changes: 94 additions & 95 deletions bin/node/src/commands/bundled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use miden_node_rpc::Rpc;
use miden_node_store::Store;
use miden_node_utils::grpc::UrlExt;
use miden_node_validator::Validator;
use miden_protocol::block::BlockSigner;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey;
use miden_protocol::utils::Deserializable;
use tokio::net::TcpListener;
Expand All @@ -22,9 +21,10 @@ use crate::commands::{
ENV_BLOCK_PROVER_URL,
ENV_ENABLE_OTEL,
ENV_GENESIS_CONFIG_FILE,
ENV_VALIDATOR_INSECURE_SECRET_KEY,
ENV_VALIDATOR_KEY,
INSECURE_VALIDATOR_KEY_HEX,
NtxBuilderConfig,
ValidatorConfig,
duration_to_human_readable_string,
};

Expand All @@ -51,12 +51,12 @@ pub enum BundledCommand {
///
/// If not provided, a predefined key is used.
#[arg(
long = "validator.insecure.secret-key",
env = ENV_VALIDATOR_INSECURE_SECRET_KEY,
value_name = "VALIDATOR_INSECURE_SECRET_KEY",
long = "validator.key",
env = ENV_VALIDATOR_KEY,
value_name = "VALIDATOR_KEY",
default_value = INSECURE_VALIDATOR_KEY_HEX
)]
validator_insecure_secret_key: String,
validator_key: String,
},

/// Runs all three node components in the same process.
Expand All @@ -82,6 +82,9 @@ pub enum BundledCommand {
#[command(flatten)]
ntx_builder: NtxBuilderConfig,

#[command(flatten)]
validator: ValidatorConfig,

/// Enables the exporting of traces for OpenTelemetry.
///
/// This can be further configured using environment variables as defined in the official
Expand All @@ -99,15 +102,6 @@ pub enum BundledCommand {
value_name = "DURATION"
)]
grpc_timeout: Duration,

/// Insecure, hex-encoded validator secret key for development and testing purposes.
#[arg(
long = "validator.insecure.secret-key",
env = ENV_VALIDATOR_INSECURE_SECRET_KEY,
value_name = "VALIDATOR_INSECURE_SECRET_KEY",
default_value = INSECURE_VALIDATOR_KEY_HEX
)]
validator_insecure_secret_key: String,
},
}

Expand All @@ -118,14 +112,14 @@ impl BundledCommand {
data_directory,
accounts_directory,
genesis_config_file,
validator_insecure_secret_key,
validator_key,
} => {
// Currently the bundled bootstrap is identical to the store's bootstrap.
crate::commands::store::StoreCommand::Bootstrap {
data_directory,
accounts_directory,
genesis_config_file,
validator_insecure_secret_key,
validator_key,
}
.handle()
.await
Expand All @@ -137,20 +131,18 @@ impl BundledCommand {
data_directory,
block_producer,
ntx_builder,
validator,
enable_otel: _,
grpc_timeout,
validator_insecure_secret_key,
} => {
let secret_key_bytes = hex::decode(validator_insecure_secret_key)?;
let signer = SecretKey::read_from_bytes(&secret_key_bytes)?;
Self::start(
rpc_url,
block_prover_url,
data_directory,
ntx_builder,
block_producer,
ntx_builder,
validator,
grpc_timeout,
signer,
)
.await
},
Expand All @@ -162,10 +154,10 @@ impl BundledCommand {
rpc_url: Url,
block_prover_url: Option<Url>,
data_directory: PathBuf,
ntx_builder: NtxBuilderConfig,
block_producer: BlockProducerConfig,
ntx_builder: NtxBuilderConfig,
validator: ValidatorConfig,
grpc_timeout: Duration,
signer: impl BlockSigner + Send + Sync + 'static,
) -> anyhow::Result<()> {
// Start listening on all gRPC urls so that inter-component connections can be created
// before each component is fully started up.
Expand All @@ -177,17 +169,19 @@ impl BundledCommand {
.await
.context("Failed to bind to RPC gRPC endpoint")?;

let block_producer_address = TcpListener::bind("127.0.0.1:0")
.await
.context("Failed to bind to block-producer gRPC endpoint")?
.local_addr()
.context("Failed to retrieve the block-producer's gRPC address")?;
let (block_producer_url, block_producer_address) = {
let socket_addr = TcpListener::bind("127.0.0.1:0")
.await
.context("Failed to bind to block-producer gRPC endpoint")?
.local_addr()
.context("Failed to retrieve the block-producer's gRPC address")?;
let url = Url::parse(&format!("http://{socket_addr}"))
.context("Failed to parse Block Producer URL")?;
(url, socket_addr)
};

let validator_address = TcpListener::bind("127.0.0.1:0")
.await
.context("Failed to bind to validator gRPC endpoint")?
.local_addr()
.context("Failed to retrieve the validator's gRPC address")?;
// Validator URL is either specified remote, or generated local.
let (validator_url, validator_socket_address) = validator.to_addresses().await?;

// Store addresses for each exposed API
let store_rpc_listener = TcpListener::bind("127.0.0.1:0")
Expand Down Expand Up @@ -231,85 +225,68 @@ impl BundledCommand {
let should_start_ntx_builder = !ntx_builder.disabled;

// Start block-producer. The block-producer's endpoint is available after loading completes.
let block_producer_id = join_set
.spawn({
let store_url = Url::parse(&format!("http://{store_block_producer_address}"))
.context("Failed to parse URL")?;
let validator_url = Url::parse(&format!("http://{validator_address}"))
.context("Failed to parse URL")?;
async move {
BlockProducer {
block_producer_address,
store_url,
validator_url,
batch_prover_url: block_producer.batch_prover_url,
batch_interval: block_producer.batch_interval,
block_interval: block_producer.block_interval,
max_batches_per_block: block_producer.max_batches_per_block,
max_txs_per_batch: block_producer.max_txs_per_batch,
grpc_timeout,
mempool_tx_capacity: block_producer.mempool_tx_capacity,
let block_producer_id = {
let validator_url = validator_url.clone();
join_set
.spawn({
let store_url = Url::parse(&format!("http://{store_block_producer_address}"))
.context("Failed to parse URL")?;
async move {
BlockProducer {
block_producer_address,
store_url,
validator_url,
batch_prover_url: block_producer.batch_prover_url,
batch_interval: block_producer.batch_interval,
block_interval: block_producer.block_interval,
max_batches_per_block: block_producer.max_batches_per_block,
max_txs_per_batch: block_producer.max_txs_per_batch,
grpc_timeout,
mempool_tx_capacity: block_producer.mempool_tx_capacity,
}
.serve()
.await
.context("failed while serving block-producer component")
}
.serve()
.await
.context("failed while serving block-producer component")
}
})
.id();
})
.id()
};

let validator_id = join_set
.spawn({
async move {
Validator {
address: validator_address,
// Start RPC component.
let rpc_id = {
let block_producer_url = block_producer_url.clone();
let validator_url = validator_url.clone();
join_set
.spawn(async move {
let store_url = Url::parse(&format!("http://{store_rpc_address}"))
.context("Failed to parse URL")?;
Rpc {
listener: grpc_rpc,
store_url,
block_producer_url: Some(block_producer_url),
validator_url,
grpc_timeout,
signer,
}
.serve()
.await
.context("failed while serving validator component")
}
})
.id();

// Start RPC component.
let rpc_id = join_set
.spawn(async move {
let store_url = Url::parse(&format!("http://{store_rpc_address}"))
.context("Failed to parse URL")?;
let block_producer_url = Url::parse(&format!("http://{block_producer_address}"))
.context("Failed to parse URL")?;
let validator_url = Url::parse(&format!("http://{validator_address}"))
.context("Failed to parse URL")?;
Rpc {
listener: grpc_rpc,
store_url,
block_producer_url: Some(block_producer_url),
validator_url,
grpc_timeout,
}
.serve()
.await
.context("failed while serving RPC component")
})
.id();
.context("failed while serving RPC component")
})
.id()
};

// Lookup table so we can identify the failed component.
let mut component_ids = HashMap::from([
(store_id, "store"),
(block_producer_id, "block-producer"),
(validator_id, "validator"),
(rpc_id, "rpc"),
]);

// Start network transaction builder. The endpoint is available after loading completes.
if should_start_ntx_builder {
let store_ntx_builder_url = Url::parse(&format!("http://{store_ntx_builder_address}"))
.context("Failed to parse URL")?;
let validator_url = Url::parse(&format!("http://{validator_address}"))
.context("Failed to parse URL")?;
let block_producer_url = Url::parse(&format!("http://{block_producer_address}"))
.context("Failed to parse URL")?;
let block_producer_url = block_producer_url.clone();
let validator_url = validator_url.clone();

let builder_config = ntx_builder.into_builder_config(
store_ntx_builder_url,
Expand All @@ -331,6 +308,28 @@ impl BundledCommand {
component_ids.insert(id, "ntx-builder");
}

// Start the Validator if we have bound a socket.
if let Some(address) = validator_socket_address {
Copy link
Contributor

Choose a reason for hiding this comment

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

The configuration decision based on the validator_socket_address is a bit obfuscating the fact that the user did not provide a validator URL but a key

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure that part of the stack should be considering the key. Key presence is considered elsewhere. This part of the stack just cares about whether to run the local instance or not

let secret_key_bytes = hex::decode(validator.validator_key)?;
let signer = SecretKey::read_from_bytes(&secret_key_bytes)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think its time we clean up our configuration. imo this check/conversion should be done before we actually start any tasks etc.

As in,

  1. First parse and validate all configuration properly, and
  2. only then begin launching tasks

This is a larger refactor though, so for this PR we can just move on.

Something else to note is that validator.url and validator.key should be mutually exclusive which we aren't enforcing at the moment.

let id = join_set
.spawn({
async move {
Validator {
address,
grpc_timeout,
signer,
data_directory,
}
.serve()
.await
.context("failed while serving validator component")
}
})
.id();
component_ids.insert(id, "validator");
}

// SAFETY: The joinset is definitely not empty.
let component_result = join_set.join_next_with_id().await.unwrap();

Expand Down
Loading
Loading