Skip to main content

miden_node_validator/server/
mod.rs

1use std::net::SocketAddr;
2use std::num::NonZeroUsize;
3use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::Context;
7use miden_node_proto::generated::validator::api_server;
8use miden_node_proto::generated::{self as proto};
9use miden_node_proto_build::validator_api_descriptor;
10use miden_node_utils::ErrorReport;
11use miden_node_utils::lru_cache::LruCache;
12use miden_node_utils::panic::catch_panic_layer_fn;
13use miden_node_utils::tracing::OpenTelemetrySpanExt;
14use miden_node_utils::tracing::grpc::grpc_trace_fn;
15use miden_protocol::block::{BlockSigner, ProposedBlock};
16use miden_protocol::transaction::{
17    ProvenTransaction,
18    TransactionHeader,
19    TransactionId,
20    TransactionInputs,
21};
22use miden_tx::utils::{Deserializable, Serializable};
23use tokio::net::TcpListener;
24use tokio_stream::wrappers::TcpListenerStream;
25use tonic::Status;
26use tower_http::catch_panic::CatchPanicLayer;
27use tower_http::trace::TraceLayer;
28use tracing::{Instrument, info_span};
29
30use crate::COMPONENT;
31use crate::block_validation::validate_block;
32use crate::tx_validation::validate_transaction;
33
34/// Number of transactions to keep in the validated transactions cache.
35const NUM_VALIDATED_TRANSACTIONS: NonZeroUsize = NonZeroUsize::new(10000).unwrap();
36
37/// A type alias for a LRU cache that stores validated transactions.
38pub type ValidatedTransactions = LruCache<TransactionId, TransactionHeader>;
39
40// VALIDATOR
41// ================================================================================
42
43/// The handle into running the gRPC validator server.
44///
45/// Facilitates the running of the gRPC server which implements the validator API.
46pub struct Validator<S> {
47    /// The address of the validator component.
48    pub address: SocketAddr,
49    /// Server-side timeout for an individual gRPC request.
50    ///
51    /// If the handler takes longer than this duration, the server cancels the call.
52    pub grpc_timeout: Duration,
53
54    /// The signer used to sign blocks.
55    pub signer: S,
56}
57
58impl<S: BlockSigner + Send + Sync + 'static> Validator<S> {
59    /// Serves the validator RPC API.
60    ///
61    /// Executes in place (i.e. not spawned) and will run indefinitely until a fatal error is
62    /// encountered.
63    pub async fn serve(self) -> anyhow::Result<()> {
64        tracing::info!(target: COMPONENT, endpoint=?self.address, "Initializing server");
65
66        let listener = TcpListener::bind(self.address)
67            .await
68            .context("failed to bind to block producer address")?;
69
70        let reflection_service = tonic_reflection::server::Builder::configure()
71            .register_file_descriptor_set(validator_api_descriptor())
72            .build_v1()
73            .context("failed to build reflection service")?;
74
75        // This is currently required for postman to work properly because
76        // it doesn't support the new version yet.
77        //
78        // See: <https://github.com/postmanlabs/postman-app-support/issues/13120>.
79        let reflection_service_alpha = tonic_reflection::server::Builder::configure()
80            .register_file_descriptor_set(validator_api_descriptor())
81            .build_v1alpha()
82            .context("failed to build reflection service")?;
83
84        // Build the gRPC server with the API service and trace layer.
85        tonic::transport::Server::builder()
86            .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
87            .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
88            .timeout(self.grpc_timeout)
89            .add_service(api_server::ApiServer::new(ValidatorServer::new(self.signer)))
90            .add_service(reflection_service)
91            .add_service(reflection_service_alpha)
92            .serve_with_incoming(TcpListenerStream::new(listener))
93            .await
94            .context("failed to serve validator API")
95    }
96}
97
98// VALIDATOR SERVER
99// ================================================================================
100
101/// The underlying implementation of the gRPC validator server.
102///
103/// Implements the gRPC API for the validator.
104struct ValidatorServer<S> {
105    signer: S,
106    validated_transactions: Arc<ValidatedTransactions>,
107}
108
109impl<S> ValidatorServer<S> {
110    fn new(signer: S) -> Self {
111        let validated_transactions =
112            Arc::new(ValidatedTransactions::new(NUM_VALIDATED_TRANSACTIONS));
113        Self { signer, validated_transactions }
114    }
115}
116
117#[tonic::async_trait]
118impl<S: BlockSigner + Send + Sync + 'static> api_server::Api for ValidatorServer<S> {
119    /// Returns the status of the validator.
120    async fn status(
121        &self,
122        _request: tonic::Request<()>,
123    ) -> Result<tonic::Response<proto::validator::ValidatorStatus>, tonic::Status> {
124        Ok(tonic::Response::new(proto::validator::ValidatorStatus {
125            version: env!("CARGO_PKG_VERSION").to_string(),
126            status: "OK".to_string(),
127        }))
128    }
129
130    /// Receives a proven transaction, then validates and stores it.
131    async fn submit_proven_transaction(
132        &self,
133        request: tonic::Request<proto::transaction::ProvenTransaction>,
134    ) -> Result<tonic::Response<()>, tonic::Status> {
135        let (tx, inputs) = info_span!("deserialize").in_scope(|| {
136            let request = request.into_inner();
137            let tx = ProvenTransaction::read_from_bytes(&request.transaction).map_err(|err| {
138                Status::invalid_argument(err.as_report_context("Invalid proven transaction"))
139            })?;
140            let inputs = request
141                .transaction_inputs
142                .ok_or(Status::invalid_argument("Missing transaction inputs"))?;
143            let inputs = TransactionInputs::read_from_bytes(&inputs).map_err(|err| {
144                Status::invalid_argument(err.as_report_context("Invalid transaction inputs"))
145            })?;
146
147            Result::<_, tonic::Status>::Ok((tx, inputs))
148        })?;
149
150        tracing::Span::current().set_attribute("transaction.id", tx.id());
151
152        // Validate the transaction.
153        let validated_tx_header = validate_transaction(tx, inputs).await.map_err(|err| {
154            Status::invalid_argument(err.as_report_context("Invalid transaction"))
155        })?;
156
157        // Register the validated transaction.
158        let tx_id = validated_tx_header.id();
159        self.validated_transactions
160            .put(tx_id, validated_tx_header)
161            .instrument(info_span!("validated_txs.insert"))
162            .await;
163
164        Ok(tonic::Response::new(()))
165    }
166
167    /// Validates a proposed block and returns the block header and body.
168    async fn sign_block(
169        &self,
170        request: tonic::Request<proto::blockchain::ProposedBlock>,
171    ) -> Result<tonic::Response<proto::blockchain::BlockSignature>, tonic::Status> {
172        let proposed_block = info_span!("deserialize").in_scope(|| {
173            let proposed_block_bytes = request.into_inner().proposed_block;
174
175            ProposedBlock::read_from_bytes(&proposed_block_bytes).map_err(|err| {
176                tonic::Status::invalid_argument(format!(
177                    "Failed to deserialize proposed block: {err}",
178                ))
179            })
180        })?;
181
182        // Validate the block.
183        let signature =
184            validate_block(proposed_block, &self.signer, self.validated_transactions.clone())
185                .await
186                .map_err(|err| {
187                    tonic::Status::invalid_argument(format!("Failed to validate block: {err}",))
188                })?;
189
190        // Send the signature.
191        info_span!("serialize").in_scope(|| {
192            let response = proto::blockchain::BlockSignature { signature: signature.to_bytes() };
193            Ok(tonic::Response::new(response))
194        })
195    }
196}