Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ miden-crypto = { version = "0.28" }
# External dependencies
anyhow = { version = "1.0" }
assert_matches = { version = "1.5" }
axum = { version = "0.8" }
backon = { version = "1.6" }
build-rs = { version = "0.3" }
chacha20poly1305 = { version = "0.11" }
Expand Down
2 changes: 1 addition & 1 deletion bin/network-monitor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ workspace = true

[dependencies]
anyhow = { workspace = true }
axum = { version = "0.8" }
axum = { workspace = true }
backon = { workspace = true }
clap = { features = ["env"], workspace = true }
futures = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions bin/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ doctest = false
anyhow = { workspace = true }
aws-config = { version = "1.8.14" }
aws-sdk-kms = { version = "1.100" }
axum = { workspace = true }
base64 = { version = "0.22" }
chacha20poly1305 = { workspace = true }
clap = { features = ["env", "string"], workspace = true }
Expand All @@ -37,6 +38,7 @@ miden-node-utils = { features = ["testing"], workspace = true }
miden-protocol = { workspace = true }
miden-tx = { features = ["concurrent"], workspace = true }
rand_core_06 = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
tokio = { features = ["macros", "net", "rt-multi-thread"], workspace = true }
tokio-stream = { features = ["net"], workspace = true }
Expand All @@ -60,3 +62,4 @@ rand = { workspace = true }
rand_chacha_03 = { workspace = true }
tempfile = { workspace = true }
tokio = { features = ["macros", "rt-multi-thread", "sync"], workspace = true }
tower = { features = ["util"], workspace = true }
4 changes: 1 addition & 3 deletions bin/validator/src/commands/issue_private_record_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ pub(super) fn issue(
.context("private record bundle is invalid")?;

let request = PrivateRecordShareRequest::for_record(&record);
// Running this filesystem-restricted command is the demo's explicit release decision.
let allow = |_: &PrivateRecordShareRequest, _: &miden_validator::StoredPrivateRecord| true;
let share = operator_key
.issue_private_record_share(&mut OsRng, &request, &record, &allow)
.issue_private_record_share(&mut OsRng, &request, &record)
.context("failed to issue private record share")?;

write_share(output, &share)
Expand Down
33 changes: 29 additions & 4 deletions bin/validator/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ use miden_validator::{
GoldenOperatorKey,
LOG_TARGET,
LocalX25519TransactionInputDecrypter,
PrivateRecordSealer,
StorageKeyEpoch,
TransactionInputDecrypter,
ValidatorSigner,
};

const ENV_DATA_DIRECTORY: &str = "MIDEN_VALIDATOR_DATA_DIRECTORY";
const ENV_LISTEN: &str = "MIDEN_VALIDATOR_LISTEN";
const ENV_ADMIN_LISTEN: &str = "MIDEN_VALIDATOR_ADMIN_LISTEN";
const ENV_SIGNING_KEY: &str = "MIDEN_VALIDATOR_SIGNING_KEY";
const ENV_SIGNING_KEY_KMS_ID: &str = "MIDEN_VALIDATOR_SIGNING_KEY_KMS_ID";
const ENV_ENCRYPTION_KEY: &str = "MIDEN_VALIDATOR_ENCRYPTION_KEY";
Expand Down Expand Up @@ -170,6 +170,10 @@ pub enum ValidatorCommand {
#[arg(long = "listen", env = ENV_LISTEN, value_name = "LISTEN")]
listen: std::net::SocketAddr,

/// Socket address at which to serve the private administration API.
#[arg(long = "admin.listen", env = ENV_ADMIN_LISTEN, value_name = "LISTEN")]
admin_listen: Option<std::net::SocketAddr>,

#[command(flatten)]
grpc_options: GrpcOptionsInternal,

Expand Down Expand Up @@ -292,6 +296,7 @@ impl ValidatorCommand {
Self::ExportPrivateRecord(options) => export_private_record::export(options).await,
Self::Start {
listen,
admin_listen,
grpc_options,
signing_key,
data_directory,
Expand All @@ -303,14 +308,17 @@ impl ValidatorCommand {
..
} => {
let address = listen;
let private_record_sealer =
PrivateRecordSealer::from_operator_key(&storage_key.load()?);
let operator_key = storage_key.load()?;
tracing::info!(
target: miden_validator::LOG_TARGET,
{
service.name = "miden-validator",
service.version = env!("CARGO_PKG_VERSION"),
validator.listen = %address,
validator.admin_listen = admin_listen.map_or_else(
|| "disabled".to_owned(),
|address| address.to_string(),
),
data.directory = %data_directory.display(),
validator.signer = if signing_key_kms_id.is_some() { "kms" } else { "local" },
sqlite.connection_pool_size = sqlite_connection_pool_size.get(),
Expand All @@ -326,8 +334,9 @@ impl ValidatorCommand {

start::start(
address,
admin_listen,
grpc_options,
start::ValidatorKeys { signer, decrypter, private_record_sealer },
start::ValidatorKeys { signer, decrypter, operator_key },
data_directory,
sqlite_connection_pool_size,
shutdown,
Expand Down Expand Up @@ -675,6 +684,22 @@ mod tests {
assert_eq!(encryption_key_kms_ciphertext, None);
}

#[test]
fn admin_listener_is_opt_in() {
let command = parse_start(&[]).expect("start without an admin listener must parse");
let ValidatorCommand::Start { admin_listen, .. } = command else {
panic!("expected the start command");
};
assert_eq!(admin_listen, None);

let command = parse_start(&["--admin.listen", "127.0.0.1:50102"])
.expect("start with an admin listener must parse");
let ValidatorCommand::Start { admin_listen, .. } = command else {
panic!("expected the start command");
};
assert_eq!(admin_listen, Some("127.0.0.1:50102".parse().unwrap()));
}

#[test]
fn encryption_key_kms_ciphertext_parses_alone() {
let command = parse_start(&["--encryption-key.kms-ciphertext", "deadbeef"])
Expand Down
38 changes: 31 additions & 7 deletions bin/validator/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@ use std::sync::Arc;
use anyhow::Context;
use miden_node_utils::clap::GrpcOptionsInternal;
use miden_node_utils::shutdown::CancellationToken;
use miden_node_utils::tasks::Tasks;
use miden_validator::{
DataDirectory,
GoldenOperatorKey,
PrivateRecordSealer,
TransactionInputDecrypter,
ValidatorAdminServer,
ValidatorServer,
ValidatorSigner,
};

pub(crate) struct ValidatorKeys {
pub(crate) signer: ValidatorSigner,
pub(crate) decrypter: Arc<dyn TransactionInputDecrypter>,
pub(crate) private_record_sealer: PrivateRecordSealer,
pub(crate) operator_key: GoldenOperatorKey,
}

// Starts the validator component.
pub async fn start(
address: SocketAddr,
admin_address: Option<SocketAddr>,
grpc_options: GrpcOptionsInternal,
keys: ValidatorKeys,
data_directory: PathBuf,
Expand All @@ -31,16 +35,36 @@ pub async fn start(
) -> anyhow::Result<()> {
let data_directory =
DataDirectory::load(data_directory).context("failed to load validator data directory")?;
ValidatorServer {
let database = miden_validator::db::load_with_pool_size(
data_directory.database_path(),
sqlite_connection_pool_size,
)
.await
.context("failed to initialize validator database")?;
let private_record_sealer = PrivateRecordSealer::from_operator_key(&keys.operator_key);
let public_server = ValidatorServer {
address,
grpc_options,
signer: keys.signer,
decrypter: keys.decrypter,
private_record_sealer: keys.private_record_sealer,
private_record_sealer,
data_directory,
sqlite_connection_pool_size,
database: database.clone(),
};

let mut tasks = Tasks::new();
tasks.spawn("validator public API", public_server.serve(shutdown.clone()));
if let Some(address) = admin_address {
let admin_server = ValidatorAdminServer {
address,
operator_key: keys.operator_key,
database,
};
tasks.spawn("validator admin API", admin_server.serve(shutdown.clone()));
}
.serve(shutdown)
.await
.context("failed while serving validator component")

tasks
.join_next_or_cancelled(shutdown)
.await
.context("failed while serving validator component")
}
2 changes: 1 addition & 1 deletion bin/validator/src/db/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ mod tests {
use super::*;

const EXPECTED_SCHEMA_HASHES: [SchemaHash; 1] = [SchemaHash::from_hex(
"384a849131983267a3a8b61d170dae7bcbec535eb4716fea7812024073f569cf",
"f2f6af5e22d8d0273524a227417339279d1a694c3802a8f3f7cc4b31e21ee035",
)];

#[test]
Expand Down
7 changes: 4 additions & 3 deletions bin/validator/src/db/migrations/001_initial.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
CREATE TABLE validated_transactions (
-- Rowid-backed local insertion order used by the private administration API.
insertion_sequence INTEGER PRIMARY KEY AUTOINCREMENT,
-- Transaction ID, unique within this validator's database.
id BLOB NOT NULL,
id BLOB NOT NULL UNIQUE,
-- Signing public key of the validator that produced this record.
validator_id BLOB NOT NULL,
-- Genesis commitment of the network that produced the transaction.
Expand All @@ -17,7 +19,6 @@ CREATE TABLE validated_transactions (
encrypted_record BLOB NOT NULL,
-- Golden EHTDH1 encryption of the key for encrypted_record.
encrypted_record_key BLOB NOT NULL,
PRIMARY KEY (id),
CHECK (length(id) = 32),
CHECK (length(validator_id) = 33),
CHECK (length(chain_id) = 32),
Expand All @@ -27,7 +28,7 @@ CREATE TABLE validated_transactions (
CHECK (length(cipher_nonce) = 24),
CHECK (length(encrypted_record) >= 16),
CHECK (length(encrypted_record_key) > 0)
) WITHOUT ROWID;
);

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.

Why are we adding back ROWID?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The admin API needs stable insertion order. The frontend reverses that order to show the newest records first. The insertion sequence is rowid backed, and the schema now states why.


CREATE INDEX idx_validated_transactions_key_epoch
ON validated_transactions(key_epoch);
Expand Down
44 changes: 40 additions & 4 deletions bin/validator/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mod sql {
include_str!("sql/load_private_records_by_key_epoch.sql");
pub(super) const LOAD_PRIVATE_RECORDS_BY_SETUP_CONTEXT: &str =
include_str!("sql/load_private_records_by_setup_context.sql");
pub(super) const LOAD_ALL_TRANSACTIONS: &str = include_str!("sql/load_all_transactions.sql");
pub(super) const TRANSACTION_EXISTS: &str = include_str!("sql/transaction_exists.sql");
pub(super) const UPSERT_BLOCK_HEADER: &str = include_str!("sql/upsert_block_header.sql");
pub(super) const LOAD_CHAIN_TIP: &str = include_str!("sql/load_chain_tip.sql");
Expand Down Expand Up @@ -188,6 +189,13 @@ pub fn load_private_records_by_setup_context(
)
}

/// Loads all validated private transactions in insertion order.
pub(crate) fn load_all_transactions(
tx: &ReadTx<'_>,
) -> Result<Vec<StoredPrivateRecord>, DatabaseError> {
tx.query(sql::LOAD_ALL_TRANSACTIONS, &[], private_record_from_row)
}

fn private_record_from_row(row: &Row<'_>) -> Result<StoredPrivateRecord, DatabaseError> {
let chain_id = fixed_32(row.get(0)?, "private record chain id")?;
let key_epoch = fixed_32(row.get(1)?, "private record key epoch")?;
Expand Down Expand Up @@ -483,6 +491,34 @@ mod tests {
assert_eq!(by_setup, vec![expected.clone()]);
}

#[tokio::test]
async fn validated_private_transactions_are_loaded_in_insertion_order() {
let temp_dir = tempfile::tempdir().expect("failed to create temp directory");
let db = setup(temp_dir.path().join("validator.sqlite3")).await.unwrap();
let transaction_ids = [
TransactionId::from_raw(Word::from([9u32, 0, 0, 0])),
TransactionId::from_raw(Word::from([1u32, 0, 0, 0])),
TransactionId::from_raw(Word::from([5u32, 0, 0, 0])),
];
let records = transaction_ids
.into_iter()
.zip([1u8, 2, 3])
.map(|(transaction_id, seed)| private_record(transaction_id, seed))
.collect::<Vec<_>>();

for record in records.clone() {
db.write("insert private record", move |tx| {
insert_validated_private_transaction(tx, &record)
})
.await
.unwrap();
}

let loaded = db.read("load all transactions", load_all_transactions).await.unwrap();

assert_eq!(loaded, records);
}

#[tokio::test]
async fn stored_private_record_opens_with_threshold_shares() {
let temp_dir = tempfile::tempdir().expect("failed to create temp directory");
Expand All @@ -507,15 +543,14 @@ mod tests {
.unwrap()
.unwrap();
let request = PrivateRecordShareRequest::for_record(&stored);
let allow = |_: &PrivateRecordShareRequest, _: &StoredPrivateRecord| true;
let mut first_rng = ChaCha20Rng::from_seed([41; 32]);
let mut second_rng = ChaCha20Rng::from_seed([42; 32]);
let shares = [
operators[0]
.issue_private_record_share(&mut first_rng, &request, &stored, &allow)
.issue_private_record_share(&mut first_rng, &request, &stored)
.unwrap(),
operators[1]
.issue_private_record_share(&mut second_rng, &request, &stored, &allow)
.issue_private_record_share(&mut second_rng, &request, &stored)
.unwrap(),
];

Expand Down Expand Up @@ -545,7 +580,8 @@ mod tests {
.unwrap()
.join("\n");

assert!(schema.contains("PRIMARY KEY (id)"));
assert!(schema.contains("insertion_sequence INTEGER PRIMARY KEY AUTOINCREMENT"));
assert!(schema.contains("id BLOB NOT NULL UNIQUE"));
assert!(schema.contains("idx_validated_transactions_key_epoch"));
assert!(schema.contains("idx_validated_transactions_setup_context_id"));
}
Expand Down
12 changes: 12 additions & 0 deletions bin/validator/src/db/sql/load_all_transactions.sql
Comment thread
huitseeker marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
chain_id,
key_epoch,
id,
validator_id,
setup_context_id,
format_version,
cipher_nonce,
encrypted_record,
encrypted_record_key
FROM validated_transactions
ORDER BY insertion_sequence;
3 changes: 1 addition & 2 deletions bin/validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ pub use private_record::{
PrivateRecordError,
PrivateRecordId,
PrivateRecordSealer,
PrivateRecordSharePolicy,
PrivateRecordShareRequest,
PrivateRecordStorageFields,
StoredPrivateRecord,
};
pub use server::ValidatorServer;
pub use server::{ValidatorAdminServer, ValidatorServer};
pub use signers::{
KmsSigner,
LocalX25519TransactionInputDecrypter,
Expand Down
Loading
Loading