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: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Private notes with the network note attachment are no longer incorrectly considered as network notes (#[#1736](https://github.com/0xMiden/node/pull/1736)).
- Fixed network monitor looping on stale wallet nonce after node restarts by re-syncing wallet state from RPC after repeated failures ([#1748](https://github.com/0xMiden/node/pull/1748)).
- Added verbose `info!`-level logging to the network transaction builder for transaction execution, note filtering failures, and transaction outcomes ([#1770](https://github.com/0xMiden/node/pull/1770)).
- Network transaction actors now share the same gRPC clients, limiting the number of file descriptors being used ([#1808](https://github.com/0xMiden/node/issues/1808)).

## v0.13.7 (2026-02-25)

Expand Down Expand Up @@ -143,7 +144,7 @@
- Network transaction builder now marks notes from any error as failed ([#1508](https://github.com/0xMiden/miden-node/pull/1508)).
- Network transaction builder now adheres to note limit set by protocol ([#1508](https://github.com/0xMiden/miden-node/pull/1508)).
- Race condition resolved in the store's `apply_block` ([#1508](https://github.com/0xMiden/miden-node/pull/1508)).
- This presented as a database locked error and in rare cases a desync between the mempool and store.
- This presented as a database locked error and in rare cases a desync between the mempool and store.

## v0.12.6 (2026-01-12)

Expand Down
14 changes: 3 additions & 11 deletions crates/ntx-builder/src/actor/execute.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;

use miden_node_proto::clients::ValidatorClient;
use miden_node_proto::generated::{self as proto};
use miden_node_utils::lru_cache::LruCache;
use miden_node_utils::tracing::OpenTelemetrySpanExt;
use miden_protocol::Word;
Expand Down Expand Up @@ -33,7 +31,6 @@ use miden_protocol::transaction::{
use miden_protocol::vm::FutureMaybeSend;
use miden_remote_prover_client::remote_prover::tx_prover::RemoteTransactionProver;
use miden_tx::auth::UnreachableAuth;
use miden_tx::utils::Serializable;
use miden_tx::{
DataStore,
DataStoreError,
Expand All @@ -56,6 +53,7 @@ use crate::COMPONENT;
use crate::actor::account_state::TransactionCandidate;
use crate::block_producer::BlockProducerClient;
use crate::store::StoreClient;
use crate::validator::ValidatorClient;

#[derive(Debug, thiserror::Error)]
pub enum NtxError {
Expand Down Expand Up @@ -309,16 +307,10 @@ impl NtxContext {
proven_tx: &ProvenTransaction,
tx_inputs: &TransactionInputs,
) -> NtxResult<()> {
let request = proto::transaction::ProvenTransaction {
transaction: proven_tx.to_bytes(),
transaction_inputs: Some(tx_inputs.to_bytes()),
};
self.validator
.clone()
.submit_proven_transaction(request)
.submit_proven_transaction(proven_tx, tx_inputs)
.await
.map_err(NtxError::Submission)?;
Ok(())
.map_err(NtxError::Submission)
}
}

Expand Down
33 changes: 11 additions & 22 deletions crates/ntx-builder/src/actor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ mod inflight_note;
mod note_state;

use std::sync::Arc;
use std::time::Duration;

use account_state::{NetworkAccountState, TransactionCandidate};
use futures::FutureExt;
use miden_node_proto::clients::{Builder, ValidatorClient};
use miden_node_proto::domain::account::NetworkAccountId;
use miden_node_proto::domain::mempool::MempoolEvent;
use miden_node_utils::ErrorReport;
Expand All @@ -21,11 +19,11 @@ use miden_protocol::transaction::TransactionId;
use miden_remote_prover_client::remote_prover::tx_prover::RemoteTransactionProver;
use tokio::sync::{AcquireError, RwLock, Semaphore, mpsc};
use tokio_util::sync::CancellationToken;
use url::Url;

use crate::block_producer::BlockProducerClient;
use crate::builder::ChainState;
use crate::store::StoreClient;
use crate::validator::ValidatorClient;

// ACTOR SHUTDOWN REASON
// ================================================================================================
Expand Down Expand Up @@ -53,13 +51,13 @@ pub enum ActorShutdownReason {
pub struct AccountActorContext {
/// Client for interacting with the store in order to load account state.
pub store: StoreClient,
/// Address of the block producer gRPC server.
pub block_producer_url: Url,
/// Address of the Validator server.
pub validator_url: Url,
/// Address of the remote prover. If `None`, transactions will be proven locally, which is
// undesirable due to the performance impact.
pub tx_prover_url: Option<Url>,
/// Client for interacting with the block producer.
pub block_producer: BlockProducerClient,
/// Client for interacting with the validator.
pub validator: ValidatorClient,
/// Client for remote transaction proving. If `None`, transactions will be proven locally,
/// which is undesirable due to the performance impact.
pub prover: Option<RemoteTransactionProver>,
/// The latest chain state that account all actors can rely on. A single chain state is shared
/// among all actors.
pub chain_state: Arc<RwLock<ChainState>>,
Expand Down Expand Up @@ -173,24 +171,15 @@ impl AccountActor {
event_rx: mpsc::Receiver<Arc<MempoolEvent>>,
cancel_token: CancellationToken,
) -> Self {
let block_producer = BlockProducerClient::new(actor_context.block_producer_url.clone());
let validator = Builder::new(actor_context.validator_url.clone())
.without_tls()
.with_timeout(Duration::from_secs(10))
.without_metadata_version()
.without_metadata_genesis()
.with_otel_context_injection()
.connect_lazy::<ValidatorClient>();
let prover = actor_context.tx_prover_url.clone().map(RemoteTransactionProver::new);
Self {
origin,
store: actor_context.store.clone(),
mode: ActorMode::NoViableNotes,
event_rx,
cancel_token,
block_producer,
validator,
prover,
block_producer: actor_context.block_producer.clone(),
validator: actor_context.validator.clone(),
prover: actor_context.prover.clone(),
chain_state: actor_context.chain_state.clone(),
script_cache: actor_context.script_cache.clone(),
}
Expand Down
11 changes: 8 additions & 3 deletions crates/ntx-builder/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use miden_protocol::block::BlockHeader;
use miden_protocol::crypto::merkle::mmr::PartialMmr;
use miden_protocol::note::NoteScript;
use miden_protocol::transaction::PartialBlockchain;
use miden_remote_prover_client::remote_prover::tx_prover::RemoteTransactionProver;
use tokio::sync::{RwLock, mpsc};
use url::Url;

Expand All @@ -20,6 +21,7 @@ use crate::actor::{AccountActorContext, AccountOrigin};
use crate::block_producer::BlockProducerClient;
use crate::coordinator::Coordinator;
use crate::store::StoreClient;
use crate::validator::ValidatorClient;

// CONSTANTS
// =================================================================================================
Expand Down Expand Up @@ -141,10 +143,13 @@ impl NetworkTransactionBuilder {
// Create chain state that will be updated by the coordinator and read by actors.
let chain_state = Arc::new(RwLock::new(ChainState::new(chain_tip_header, chain_mmr)));

let validator = ValidatorClient::new(self.validator_url.clone());
let prover = self.tx_prover_url.clone().map(RemoteTransactionProver::new);

let actor_context = AccountActorContext {
block_producer_url: self.block_producer_url.clone(),
validator_url: self.validator_url.clone(),
tx_prover_url: self.tx_prover_url.clone(),
block_producer: block_producer.clone(),
validator,
prover,
chain_state: chain_state.clone(),
store: store.clone(),
script_cache: self.script_cache.clone(),
Expand Down
1 change: 1 addition & 0 deletions crates/ntx-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod block_producer;
mod builder;
mod coordinator;
mod store;
mod validator;

pub use builder::NetworkTransactionBuilder;

Expand Down
56 changes: 56 additions & 0 deletions crates/ntx-builder/src/validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::time::Duration;

use miden_node_proto::clients::{Builder, ValidatorClient as InnerValidatorClient};
use miden_node_proto::generated::{self as proto};
use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
use miden_tx::utils::Serializable;
use tonic::Status;
use tracing::{info, instrument};
use url::Url;

use crate::COMPONENT;

// CLIENT
// ================================================================================================

/// Interface to the validator's gRPC API.
///
/// Thin wrapper around the generated gRPC client which encapsulates the connection
/// configuration and improves type safety. Cloning this client shares the underlying
/// gRPC channel.
#[derive(Clone, Debug)]
pub struct ValidatorClient {
client: InnerValidatorClient,
}

impl ValidatorClient {
/// Creates a new validator client with a lazy connection and a 10-second timeout.
pub fn new(validator_url: Url) -> Self {
info!(target: COMPONENT, validator_endpoint = %validator_url, "Initializing validator client with lazy connection");

let validator = Builder::new(validator_url)
.without_tls()
.with_timeout(Duration::from_secs(10))
.without_metadata_version()
.without_metadata_genesis()
.with_otel_context_injection()
.connect_lazy::<InnerValidatorClient>();

Self { client: validator }
}

/// Submits a proven transaction with its inputs to the validator for re-execution.
#[instrument(target = COMPONENT, name = "ntx.validator.client.submit_proven_transaction", skip_all, err)]
pub async fn submit_proven_transaction(
&self,
proven_tx: &ProvenTransaction,
tx_inputs: &TransactionInputs,
) -> Result<(), Status> {
let request = proto::transaction::ProvenTransaction {
transaction: proven_tx.to_bytes(),
transaction_inputs: Some(tx_inputs.to_bytes()),
};
self.client.clone().submit_proven_transaction(request).await?;
Ok(())
}
}
Loading