Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
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.

1 change: 1 addition & 0 deletions bin/node/src/commands/bundled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ impl BundledCommand {
address: validator_address,
grpc_timeout,
signer,
data_directory,
}
.serve()
.await
Expand Down
25 changes: 20 additions & 5 deletions bin/node/src/commands/validator.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::path::PathBuf;
use std::time::Duration;

use anyhow::Context;
Expand All @@ -9,6 +10,7 @@ use url::Url;

use crate::commands::{
DEFAULT_TIMEOUT,
ENV_DATA_DIRECTORY,
ENV_ENABLE_OTEL,
ENV_VALIDATOR_INSECURE_SECRET_KEY,
ENV_VALIDATOR_URL,
Expand Down Expand Up @@ -40,6 +42,10 @@ pub enum ValidatorCommand {
)]
grpc_timeout: Duration,

/// Directory in which to store the database and raw block data.
#[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")]
data_directory: PathBuf,

/// Insecure, hex-encoded validator secret key for development and testing purposes.
///
/// If not provided, a predefined key is used.
Expand All @@ -51,18 +57,27 @@ pub enum ValidatorCommand {
impl ValidatorCommand {
pub async fn handle(self) -> anyhow::Result<()> {
let Self::Start {
url, grpc_timeout, insecure_secret_key, ..
url,
grpc_timeout,
insecure_secret_key,
data_directory,
..
} = self;

let address =
url.to_socket().context("Failed to extract socket address from validator URL")?;

let signer = SecretKey::read_from_bytes(hex::decode(insecure_secret_key)?.as_ref())?;

Validator { address, grpc_timeout, signer }
.serve()
.await
.context("failed while serving validator component")
Validator {
address,
grpc_timeout,
signer,
data_directory,
}
.serve()
.await
.context("failed while serving validator component")
}

pub fn is_open_telemetry_enabled(&self) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions crates/store/src/db/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ impl ConnectionManagerError {
/// Create a connection manager with per-connection setup
///
/// Particularly, `foreign_key` checks are enabled and using a write-append-log for journaling.
pub(crate) struct ConnectionManager {
pub struct ConnectionManager {
pub(crate) manager: deadpool_diesel::sqlite::Manager,
}

impl ConnectionManager {
pub(crate) fn new(database_path: &str) -> Self {
pub fn new(database_path: &str) -> Self {
let manager = deadpool_diesel::sqlite::Manager::new(
database_path.to_owned(),
deadpool_diesel::sqlite::Runtime::Tokio1,
Expand Down
9 changes: 7 additions & 2 deletions crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ impl From<NoteRecord> for NoteSyncRecord {
}

impl Db {
/// Creates a new database instance with the provided connection pool.
pub fn new(pool: deadpool_diesel::Pool<ConnectionManager>) -> Self {
Self { pool }
}

/// Creates a new database and inserts the genesis block.
#[instrument(
target = COMPONENT,
Expand Down Expand Up @@ -260,7 +265,7 @@ impl Db {
}

/// Create and commit a transaction with the queries added in the provided closure
pub(crate) async fn transact<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
pub async fn transact<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
where
Q: Send
+ for<'a, 't> FnOnce(&'a mut SqliteConnection) -> std::result::Result<R, E>
Expand All @@ -285,7 +290,7 @@ impl Db {
}

/// Run the query _without_ a transaction
pub(crate) async fn query<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
pub async fn query<R, E, Q, M>(&self, msg: M, query: Q) -> std::result::Result<R, E>
where
Q: Send + FnOnce(&mut SqliteConnection) -> std::result::Result<R, E> + 'static,
R: Send + 'static,
Expand Down
2 changes: 1 addition & 1 deletion crates/store/src/db/models/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub struct DatabaseTypeConversionError {
/// Convert from and to it's database representation and back
///
/// We do not assume sanity of DB types.
pub(crate) trait SqlTypeConvert: Sized {
pub trait SqlTypeConvert: Sized {
type Raw: Sized;

fn to_raw_sql(self) -> Self::Raw;
Expand Down
4 changes: 4 additions & 0 deletions crates/store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ pub mod state;
#[cfg(feature = "rocksdb")]
pub use accounts::PersistentAccountTree;
pub use accounts::{AccountTreeWithHistory, HistoricalError, InMemoryAccountTree};
pub use db::Db;
pub use db::manager::ConnectionManager;
pub use db::models::conv::SqlTypeConvert;
pub use errors::{DatabaseError, DatabaseSetupError};
pub use genesis::GenesisState;
pub use server::{DataDirectory, Store};

Expand Down
4 changes: 4 additions & 0 deletions crates/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ workspace = true

[dependencies]
anyhow = { workspace = true }
deadpool-diesel = { features = ["sqlite"], version = "0.6" }
diesel = { features = ["numeric", "sqlite"], version = "2.2" }
diesel_migrations = { features = ["sqlite"], version = "2.2" }
miden-node-proto = { workspace = true }
miden-node-store = { workspace = true }
miden-node-proto-build = { features = ["internal"], workspace = true }
miden-node-utils = { features = ["testing"], workspace = true }
miden-protocol = { workspace = true }
Expand Down
9 changes: 9 additions & 0 deletions crates/validator/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This build.rs is required to trigger the `diesel_migrations::embed_migrations!` proc-macro in
// `validator/src/db/migrations.rs` to include the latest version of the migrations into the binary, see <https://docs.rs/diesel_migrations/latest/diesel_migrations/macro.embed_migrations.html#automatic-rebuilds>.
fn main() {
println!("cargo:rerun-if-changed=./src/db/migrations");
// If we do one re-write, the default rules are disabled,
// hence we need to trigger explicitly on `Cargo.toml`.
// <https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed>
println!("cargo:rerun-if-changed=Cargo.toml");
}
5 changes: 5 additions & 0 deletions crates/validator/diesel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/db/schema.rs"
58 changes: 43 additions & 15 deletions crates/validator/src/block_validation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::sync::Arc;
use std::collections::HashMap;

use miden_node_store::{DatabaseError, Db};
use miden_protocol::block::{BlockNumber, BlockSigner, ProposedBlock};
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::Signature;
use miden_protocol::errors::ProposedBlockError;
use miden_protocol::transaction::TransactionId;
use tracing::{Instrument, info_span};
use miden_protocol::transaction::{TransactionHeader, TransactionId};
use tracing::info_span;

use crate::server::ValidatedTransactions;
use crate::db::select_transactions;

// BLOCK VALIDATION ERROR
// ================================================================================================
Expand All @@ -15,8 +16,19 @@ use crate::server::ValidatedTransactions;
pub enum BlockValidationError {
#[error("transaction {0} in block {1} has not been validated")]
TransactionNotValidated(TransactionId, BlockNumber),
#[error(
"the proposed transaction {proposed_tx} does not match the validated transaction {validated_tx}"
)]
TransactionMismatch {
proposed_tx: TransactionId,
validated_tx: TransactionId,
},
#[error("failed to build block")]
BlockBuildingFailed(#[from] ProposedBlockError),
#[error("failed to select transactions")]
DatabaseError(#[from] DatabaseError),
#[error("internal error: {0}")]
Other(String),
}

// BLOCK VALIDATION
Expand All @@ -29,23 +41,39 @@ pub enum BlockValidationError {
pub async fn validate_block<S: BlockSigner>(
proposed_block: ProposedBlock,
signer: &S,
validated_transactions: Arc<ValidatedTransactions>,
db: &Db,
) -> Result<Signature, BlockValidationError> {
// Check that all transactions in the proposed block have been validated
let verify_span = info_span!("verify_transactions");
for tx_header in proposed_block.transactions() {
let tx_id = tx_header.id();
// TODO: LruCache is a poor abstraction since it locks many times.
if validated_transactions
.get(&tx_id)
.instrument(verify_span.clone())
.await
.is_none()
{

// Create a map of transactions from the proposed block.
let proposed_transactions = proposed_block
.transactions()
.map(|header| (header.id(), header.clone()))
.collect::<HashMap<TransactionId, TransactionHeader>>();
// Retrieve all validated transactions pertaining to the proposed block.
let tx_ids = proposed_block.transactions().map(TransactionHeader::id).collect::<Vec<_>>();
let query_tx_ids = tx_ids.clone();
let validated_transactions = db
.transact("select_transactions", move |conn| select_transactions(conn, &query_tx_ids))
.await?;

// Check that every transaction from the proposed block has been validated.
for tx_id in tx_ids {
let Some(validated_tx) = validated_transactions.get(&tx_id) else {
return Err(BlockValidationError::TransactionNotValidated(
tx_id,
proposed_block.block_num(),
));
};
// Check that the proposed and validated transactions are equal.
let proposed_tx = proposed_transactions.get(&tx_id).ok_or(BlockValidationError::Other(
"proposed transactions mapped incorrectly".into(),
))?;
if validated_tx != proposed_tx {
return Err(BlockValidationError::TransactionMismatch {
proposed_tx: proposed_tx.id(),
validated_tx: validated_tx.id(),
});
}
}

Expand Down
25 changes: 25 additions & 0 deletions crates/validator/src/db/migrations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use diesel::SqliteConnection;
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
use miden_node_store::DatabaseError;
use tracing::instrument;

use crate::COMPONENT;

// The rebuild is automatically triggered by `build.rs` as described in
// <https://docs.rs/diesel_migrations/latest/diesel_migrations/macro.embed_migrations.html#automatic-rebuilds>.
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("src/db/migrations");

#[instrument(level = "debug", target = COMPONENT, skip_all, err)]
pub fn apply_migrations(conn: &mut SqliteConnection) -> std::result::Result<(), DatabaseError> {
let migrations = conn.pending_migrations(MIGRATIONS).expect("In memory migrations never fail");
tracing::info!(target = COMPONENT, "Applying {} migration(s)", migrations.len());

let Err(e) = conn.run_pending_migrations(MIGRATIONS) else {
return Ok(());
};
tracing::warn!(target = COMPONENT, "Failed to apply migration: {e:?}");
conn.revert_last_migration(MIGRATIONS)
.expect("Duality is maintained by the developer");

Ok(())
}
Empty file.
15 changes: 15 additions & 0 deletions crates/validator/src/db/migrations/2025062000000_setup/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE TABLE transactions (
transaction_id BLOB NOT NULL,
account_id BLOB NOT NULL,
block_num INTEGER NOT NULL, -- Block number in which the transaction was included.
initial_state_commitment BLOB NOT NULL, -- State of the account before applying the transaction.
final_state_commitment BLOB NOT NULL, -- State of the account after applying the transaction.
input_notes BLOB NOT NULL, -- Serialized vector with the Nullifier of the input notes.
output_notes BLOB NOT NULL, -- Serialized vector with the NoteId of the output notes.
size_in_bytes INTEGER NOT NULL, -- Estimated size of the row in bytes, considering the size of the input and output notes.

PRIMARY KEY (transaction_id)
) WITHOUT ROWID;

CREATE INDEX idx_transactions_account_id ON transactions(account_id);
CREATE INDEX idx_transactions_block_num ON transactions(block_num);
Loading
Loading