diff --git a/CHANGELOG.md b/CHANGELOG.md index 19957fb96..493570753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fixed `TransactionHeader` serialization for row insertion on database & fixed transaction cursor on retrievals ([#1701](https://github.com/0xMiden/node/issues/1701)). - Added KMS signing support in validator ([#1677](https://github.com/0xMiden/node/pull/1677)). - Added per-IP gRPC rate limiting across services as well as global concurrent connection limit ([#1746](https://github.com/0xMiden/node/issues/1746)). +- Network transaction actors now share the same gRPC clients, limiting the number of file descriptors being used ([#1806](https://github.com/0xMiden/node/issues/1806)). ### Changes diff --git a/crates/ntx-builder/src/actor/execute.rs b/crates/ntx-builder/src/actor/execute.rs index d06c283c1..aa488c284 100644 --- a/crates/ntx-builder/src/actor/execute.rs +++ b/crates/ntx-builder/src/actor/execute.rs @@ -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::ErrorReport; use miden_node_utils::lru_cache::LruCache; use miden_node_utils::tracing::OpenTelemetrySpanExt; @@ -35,7 +33,6 @@ use miden_protocol::transaction::{ use miden_protocol::vm::FutureMaybeSend; use miden_remote_prover_client::RemoteTransactionProver; use miden_tx::auth::UnreachableAuth; -use miden_tx::utils::Serializable; use miden_tx::{ DataStore, DataStoreError, @@ -56,7 +53,7 @@ use tracing::{Instrument, instrument}; use crate::COMPONENT; use crate::actor::candidate::TransactionCandidate; -use crate::clients::{BlockProducerClient, StoreClient}; +use crate::clients::{BlockProducerClient, StoreClient, ValidatorClient}; use crate::db::Db; #[derive(Debug, thiserror::Error)] @@ -327,16 +324,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) } } diff --git a/crates/ntx-builder/src/actor/mod.rs b/crates/ntx-builder/src/actor/mod.rs index eadbea43d..e1e2a34d0 100644 --- a/crates/ntx-builder/src/actor/mod.rs +++ b/crates/ntx-builder/src/actor/mod.rs @@ -8,7 +8,6 @@ use std::time::Duration; use anyhow::Context; use candidate::TransactionCandidate; use futures::FutureExt; -use miden_node_proto::clients::{Builder, ValidatorClient}; use miden_node_proto::domain::account::NetworkAccountId; use miden_node_utils::ErrorReport; use miden_node_utils::lru_cache::LruCache; @@ -21,11 +20,10 @@ use miden_remote_prover_client::RemoteTransactionProver; use miden_tx::FailedNote; use tokio::sync::{Notify, RwLock, Semaphore, mpsc}; use tokio_util::sync::CancellationToken; -use url::Url; use crate::NoteError; use crate::chain_state::ChainState; -use crate::clients::{BlockProducerClient, StoreClient}; +use crate::clients::{BlockProducerClient, StoreClient, ValidatorClient}; use crate::db::Db; // ACTOR REQUESTS @@ -54,13 +52,13 @@ pub enum ActorRequest { 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, + /// 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, /// The latest chain state that account all actors can rely on. A single chain state is shared /// among all actors. pub chain_state: Arc>, @@ -101,9 +99,9 @@ impl AccountActorContext { let (request_tx, _request_rx) = mpsc::channel(1); Self { - block_producer_url: url.clone(), - validator_url: url.clone(), - tx_prover_url: None, + block_producer: BlockProducerClient::new(url.clone()), + validator: ValidatorClient::new(url.clone()), + prover: None, chain_state, store: StoreClient::new(url), script_cache: LruCache::new(NonZeroUsize::new(1).unwrap()), @@ -229,15 +227,6 @@ impl AccountActor { notify: Arc, 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::(); - let prover = actor_context.tx_prover_url.clone().map(RemoteTransactionProver::new); Self { origin, store: actor_context.store.clone(), @@ -245,9 +234,9 @@ impl AccountActor { mode: ActorMode::NoViableNotes, notify, 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(), max_notes_per_tx: actor_context.max_notes_per_tx, diff --git a/crates/ntx-builder/src/clients/mod.rs b/crates/ntx-builder/src/clients/mod.rs index fd8b5ea0f..19814602b 100644 --- a/crates/ntx-builder/src/clients/mod.rs +++ b/crates/ntx-builder/src/clients/mod.rs @@ -1,5 +1,7 @@ mod block_producer; mod store; +mod validator; pub use block_producer::BlockProducerClient; pub use store::StoreClient; +pub use validator::ValidatorClient; diff --git a/crates/ntx-builder/src/clients/validator.rs b/crates/ntx-builder/src/clients/validator.rs new file mode 100644 index 000000000..6488c6167 --- /dev/null +++ b/crates/ntx-builder/src/clients/validator.rs @@ -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::(); + + 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(()) + } +} diff --git a/crates/ntx-builder/src/lib.rs b/crates/ntx-builder/src/lib.rs index d6d8c5a01..48930be26 100644 --- a/crates/ntx-builder/src/lib.rs +++ b/crates/ntx-builder/src/lib.rs @@ -7,12 +7,13 @@ use actor::AccountActorContext; use anyhow::Context; use builder::MempoolEventStream; use chain_state::ChainState; -use clients::{BlockProducerClient, StoreClient}; +use clients::{BlockProducerClient, StoreClient, ValidatorClient}; use coordinator::Coordinator; use db::Db; use futures::TryStreamExt; use miden_node_utils::ErrorReport; use miden_node_utils::lru_cache::LruCache; +use miden_remote_prover_client::RemoteTransactionProver; use tokio::sync::{RwLock, mpsc}; use url::Url; @@ -247,6 +248,8 @@ impl NtxBuilderConfig { let store = StoreClient::new(self.store_url.clone()); let block_producer = BlockProducerClient::new(self.block_producer_url.clone()); + let validator = ValidatorClient::new(self.validator_url.clone()); + let prover = self.tx_prover_url.clone().map(RemoteTransactionProver::new); // Subscribe to mempool first to ensure we don't miss any events. The subscription // replays all inflight transactions, so the subscriber's state is fully reconstructed. @@ -272,9 +275,9 @@ impl NtxBuilderConfig { let (request_tx, actor_request_rx) = mpsc::channel(1); 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,