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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
- [BREAKING] Renamed `ExplorerStatusDetails` fields in the network monitor's `/status` payload from `number_of_*` to `total_*` (`total_transactions`, `total_nullifiers`, `total_notes`, `total_account_updates`). The values now represent network-wide cumulative totals from the explorer's `overviewStats` query instead of last-block counts.
- [BREAKING] Removed `--wallet-filepath` / `--counter-filepath` flags and the `MIDEN_MONITOR_WALLET_FILEPATH` / `MIDEN_MONITOR_COUNTER_FILEPATH` env vars from the network monitor. The monitor now keeps wallet and counter accounts fully in memory and regenerates them on every startup; the dashboard's counter value resets to zero on restart.
- Added `--counter-pending-unhealthy-threshold` (env `MIDEN_MONITOR_COUNTER_PENDING_UNHEALTHY_THRESHOLD`, default `5`) to the network monitor: the Network Transactions card now flips unhealthy when the gap between expected and observed counter values stays above the threshold for three consecutive polls.
- Allowed network transaction submission conditionally via the gRPC `SubmitProvenTx` and `SubmitProvenTxBatch` endpoints: the NTX builder can now send a key in the `x-miden-network-tx-auth` header that enables submitting network transactions ([#2131](https://github.com/0xMiden/node/issues/2131)).

## v0.14.11 (TBD)

- 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)).
Expand Down
9 changes: 9 additions & 0 deletions bin/node/src/commands/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ pub struct RpcOptions {
#[arg(long = "rpc.listen", env = "MIDEN_NODE_RPC_LISTEN", value_name = "LISTEN")]
pub listen: SocketAddr,

/// Optional metadata header value for internal network-transaction RPC authentication.
#[arg(
long = "rpc.network-tx-auth-header-value",
env = "MIDEN_NODE_RPC_NETWORK_TX_AUTH_HEADER_VALUE",
value_name = "VALUE",
help_heading = super::section::RPC_CONFIGURATION_HELP_HEADING
)]
pub network_tx_auth_header_value: Option<String>,
Comment on lines +21 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The idea is that this would be a secret value that internal RPC nodes would be initialized with, but external RPC nodes wouldn't know - right?

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.

Correct yes

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.

Or in the simplest case, only the sequencer so long as one ensures that the ntx builder is connected to it and not a random internal full node


#[command(flatten)]
pub grpc: RpcGrpcOptions,

Expand Down
22 changes: 18 additions & 4 deletions bin/ntx-builder/src/clients/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use miden_protocol::transaction::{AccountInputs, ProvenTransaction, TransactionI
use miden_protocol::utils::serde::{Deserializable, Serializable};
use thiserror::Error;
use tonic::Status;
use tonic::metadata::AsciiMetadataValue;
use tracing::{info, instrument};
use url::Url;

Expand All @@ -41,15 +42,28 @@ impl RpcClient {
/// `backoff_initial` / `backoff_max` configure the exponential backoff schedule applied to
/// `block_subscription` retries (the only operation that retries today).
pub fn new(rpc_url: Url, backoff_initial: Duration, backoff_max: Duration) -> Self {
Self::new_with_auth(rpc_url, None, backoff_initial, backoff_max)
}

/// Creates a new client with an optional metadata header for internal RPC authentication.
pub fn new_with_auth(
rpc_url: Url,
rpc_auth_header_value: Option<AsciiMetadataValue>,
backoff_initial: Duration,
backoff_max: Duration,
) -> Self {
info!(target: COMPONENT, rpc_endpoint = %rpc_url, "Initializing RPC client");

let rpc = Builder::new(rpc_url)
let builder = Builder::new(rpc_url)
.without_tls()
.without_timeout()
.without_metadata_version()
.without_metadata_genesis()
.with_otel_context_injection()
.connect_lazy::<InnerRpcClient>();
.without_metadata_genesis();
let builder = match rpc_auth_header_value {
Some(value) => builder.with_auth_header_value(value),
None => builder.without_auth_header(),
};
let rpc = builder.with_otel_context_injection().connect_lazy::<InnerRpcClient>();

let backoff = ExponentialBuilder::default()
.with_min_delay(backoff_initial)
Expand Down
44 changes: 44 additions & 0 deletions bin/ntx-builder/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use anyhow::Context;
use clap::Parser;
use miden_node_utils::clap::duration_to_human_readable_string;
use tokio::net::TcpListener;
use tonic::metadata::AsciiMetadataValue;
use url::Url;

const ENV_ENABLE_OTEL: &str = "MIDEN_NODE_ENABLE_OTEL";
const ENV_DATA_DIRECTORY: &str = "MIDEN_NODE_DATA_DIRECTORY";
const ENV_LISTEN: &str = "MIDEN_NODE_NTX_BUILDER_LISTEN";
const ENV_RPC_URL: &str = "MIDEN_NODE_NTX_BUILDER_RPC_URL";
const ENV_RPC_AUTH_HEADER_VALUE: &str = "MIDEN_NODE_NTX_BUILDER_RPC_AUTH_HEADER_VALUE";
const ENV_TX_PROVER_URL: &str = "MIDEN_NODE_NTX_BUILDER_NTX_PROVER_URL";
const ENV_SCRIPT_CACHE_SIZE: &str = "MIDEN_NODE_NTX_BUILDER_SCRIPT_CACHE_SIZE";
const ENV_MAX_CYCLES: &str = "MIDEN_NODE_NTX_BUILDER_MAX_CYCLES";
Expand All @@ -35,6 +37,14 @@ pub enum NtxBuilderCommand {
#[arg(long = "rpc.url", alias = "store.url", env = ENV_RPC_URL, value_name = "URL")]
rpc_url: Url,

/// Optional value for the fixed `x-miden-network-tx-auth` metadata header.
#[arg(
long = "rpc.auth-header-value",
env = ENV_RPC_AUTH_HEADER_VALUE,
value_name = "VALUE"
)]
rpc_auth_header_value: Option<AsciiMetadataValue>,

/// The remote transaction prover's gRPC url. If unset, will default to running a prover
/// in-process which is expensive.
#[arg(long = "tx-prover.url", env = ENV_TX_PROVER_URL, value_name = "URL")]
Expand Down Expand Up @@ -108,6 +118,7 @@ impl NtxBuilderCommand {
let Self::Start {
listen,
rpc_url,
rpc_auth_header_value,
tx_prover_url,
script_cache_size,
idle_timeout,
Expand All @@ -131,6 +142,10 @@ impl NtxBuilderCommand {
.with_max_account_crashes(max_account_crashes)
.with_max_cycles(max_tx_cycles)
.with_sqlite_connection_pool_size(sqlite_connection_pool_size);
let config = match rpc_auth_header_value {
Some(value) => config.with_rpc_auth_header(value),
None => config,
};

config
.build()
Expand All @@ -146,3 +161,32 @@ impl NtxBuilderCommand {
*enable_otel
}
}

#[cfg(test)]
mod tests {
use clap::Parser;
use tonic::metadata::AsciiMetadataValue;

use super::NtxBuilderCommand;

#[test]
fn start_command_parses_rpc_auth_header_options() {
let command = NtxBuilderCommand::try_parse_from([
"miden-ntx-builder",
"start",
"--listen",
"127.0.0.1:8080",
"--rpc.url",
"http://127.0.0.1:57291",
"--rpc.auth-header-value",
"secret-token",
"--data-directory",
"/tmp/miden-ntx-builder",
])
.expect("command should parse");

let NtxBuilderCommand::Start { rpc_auth_header_value, .. } = command;

assert_eq!(rpc_auth_header_value, Some(AsciiMetadataValue::from_static("secret-token")));
}
}
65 changes: 59 additions & 6 deletions bin/ntx-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use futures::StreamExt;
use miden_node_utils::ErrorReport;
use miden_protocol::block::BlockNumber;
use miden_protocol::crypto::merkle::mmr::PartialMmr;
use tonic::metadata::AsciiMetadataValue;
use url::Url;

use crate::committed_block::CommittedBlockEffects;
Expand Down Expand Up @@ -96,6 +97,9 @@ pub struct NtxBuilderConfig {
/// Address of the node RPC gRPC server.
pub rpc_url: Url,

/// Optional auth header value injected into internal RPC requests.
pub rpc_auth_header: Option<AsciiMetadataValue>,

/// Address of the remote transaction prover. If `None`, transactions will be proven locally.
pub tx_prover_url: Option<Url>,

Expand Down Expand Up @@ -157,6 +161,7 @@ impl NtxBuilderConfig {
pub fn new(rpc_url: Url, database_filepath: PathBuf) -> Self {
Self {
rpc_url,
rpc_auth_header: None,
tx_prover_url: None,
script_cache_size: DEFAULT_SCRIPT_CACHE_SIZE,
max_concurrent_txs: DEFAULT_MAX_CONCURRENT_TXS,
Expand All @@ -183,6 +188,13 @@ impl NtxBuilderConfig {
self
}

/// Sets the optional auth header value to inject into internal RPC requests.
#[must_use]
pub fn with_rpc_auth_header(mut self, value: AsciiMetadataValue) -> Self {
self.rpc_auth_header = Some(value);
self
}

/// Sets the script cache size.
#[must_use]
pub fn with_script_cache_size(mut self, size: NonZeroUsize) -> Self {
Expand Down Expand Up @@ -288,19 +300,27 @@ impl NtxBuilderConfig {
/// - The RPC connection fails (after retries)
/// - The genesis block cannot be read from the subscription on a fresh start
pub async fn build(self) -> anyhow::Result<NetworkTransactionBuilder> {
let rpc = match self.rpc_auth_header.clone() {
Some(rpc_auth_header_value) => RpcClient::new_with_auth(
self.rpc_url.clone(),
Some(rpc_auth_header_value),
self.request_backoff_initial,
self.request_backoff_max,
),
None => RpcClient::new(
self.rpc_url.clone(),
self.request_backoff_initial,
self.request_backoff_max,
),
};

// Set up the database (bootstrap + connection pool).
let db = Db::setup_with_pool_size(
self.database_filepath.clone(),
self.sqlite_connection_pool_size,
)
.await?;

let rpc = RpcClient::new(
self.rpc_url.clone(),
self.request_backoff_initial,
self.request_backoff_max,
);

// Decide where to start the subscription. On resume we load the persisted chain state; on
// fresh start we begin at genesis and bootstrap inline below.
let stored_chain_state =
Expand Down Expand Up @@ -358,3 +378,36 @@ impl NtxBuilderConfig {
))
}
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use tonic::metadata::AsciiMetadataValue;
use url::Url;

use super::NtxBuilderConfig;

#[test]
fn ntx_builder_config_default_has_no_rpc_auth_header() {
let config = NtxBuilderConfig::new(
Url::parse("http://127.0.0.1:57291").expect("valid url"),
PathBuf::from("ntx-builder.sqlite3"),
);

assert_eq!(config.rpc_auth_header, None);
}

#[test]
fn ntx_builder_config_with_rpc_auth_header_stores_value() {
let secret_token = AsciiMetadataValue::from_static("secret-token");

let config = NtxBuilderConfig::new(
Url::parse("http://127.0.0.1:57291").expect("valid url"),
PathBuf::from("ntx-builder.sqlite3"),
)
.with_rpc_auth_header(secret_token.clone());

assert_eq!(config.rpc_auth_header, Some(secret_token));
}
}
Loading
Loading