miden_node_validator/server/
mod.rs1use 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
34const NUM_VALIDATED_TRANSACTIONS: NonZeroUsize = NonZeroUsize::new(10000).unwrap();
36
37pub type ValidatedTransactions = LruCache<TransactionId, TransactionHeader>;
39
40pub struct Validator<S> {
47 pub address: SocketAddr,
49 pub grpc_timeout: Duration,
53
54 pub signer: S,
56}
57
58impl<S: BlockSigner + Send + Sync + 'static> Validator<S> {
59 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 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 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
98struct 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 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 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 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 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 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 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 info_span!("serialize").in_scope(|| {
192 let response = proto::blockchain::BlockSignature { signature: signature.to_bytes() };
193 Ok(tonic::Response::new(response))
194 })
195 }
196}