miden_node_validator/server/
mod.rs1use std::net::SocketAddr;
2use std::path::PathBuf;
3use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::Context;
7use miden_node_db::Db;
8use miden_node_proto::generated::validator::api_server;
9use miden_node_proto::generated::{self as proto};
10use miden_node_proto_build::validator_api_descriptor;
11use miden_node_utils::ErrorReport;
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::ProposedBlock;
16use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
17use miden_tx::utils::{Deserializable, Serializable};
18use tokio::net::TcpListener;
19use tokio_stream::wrappers::TcpListenerStream;
20use tonic::Status;
21use tower_http::catch_panic::CatchPanicLayer;
22use tower_http::trace::TraceLayer;
23use tracing::{info_span, instrument};
24
25use crate::block_validation::validate_block;
26use crate::db::{insert_transaction, load};
27use crate::tx_validation::validate_transaction;
28use crate::{COMPONENT, ValidatorSigner};
29
30pub struct Validator {
37 pub address: SocketAddr,
39 pub grpc_timeout: Duration,
43
44 pub signer: ValidatorSigner,
46
47 pub data_directory: PathBuf,
49}
50
51impl Validator {
52 pub async fn serve(self) -> anyhow::Result<()> {
57 tracing::info!(target: COMPONENT, endpoint=?self.address, "Initializing server");
58
59 let db = load(self.data_directory.join("validator.sqlite3"))
61 .await
62 .context("failed to initialize validator database")?;
63
64 let listener = TcpListener::bind(self.address)
65 .await
66 .context("failed to bind to block producer address")?;
67
68 let reflection_service = tonic_reflection::server::Builder::configure()
69 .register_file_descriptor_set(validator_api_descriptor())
70 .build_v1()
71 .context("failed to build reflection service")?;
72
73 let reflection_service_alpha = tonic_reflection::server::Builder::configure()
78 .register_file_descriptor_set(validator_api_descriptor())
79 .build_v1alpha()
80 .context("failed to build reflection service")?;
81
82 tonic::transport::Server::builder()
84 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
85 .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
86 .timeout(self.grpc_timeout)
87 .add_service(api_server::ApiServer::new(ValidatorServer::new(self.signer, db)))
88 .add_service(reflection_service)
89 .add_service(reflection_service_alpha)
90 .serve_with_incoming(TcpListenerStream::new(listener))
91 .await
92 .context("failed to serve validator API")
93 }
94}
95
96struct ValidatorServer {
103 signer: ValidatorSigner,
104 db: Arc<Db>,
105}
106
107impl ValidatorServer {
108 fn new(signer: ValidatorSigner, db: Db) -> Self {
109 Self { signer, db: db.into() }
110 }
111}
112
113#[tonic::async_trait]
114impl api_server::Api for ValidatorServer {
115 async fn status(
117 &self,
118 _request: tonic::Request<()>,
119 ) -> Result<tonic::Response<proto::validator::ValidatorStatus>, tonic::Status> {
120 Ok(tonic::Response::new(proto::validator::ValidatorStatus {
121 version: env!("CARGO_PKG_VERSION").to_string(),
122 status: "OK".to_string(),
123 }))
124 }
125
126 #[instrument(target = COMPONENT, skip_all, err)]
128 async fn submit_proven_transaction(
129 &self,
130 request: tonic::Request<proto::transaction::ProvenTransaction>,
131 ) -> Result<tonic::Response<()>, tonic::Status> {
132 let (tx, inputs) = info_span!("deserialize").in_scope(|| {
133 let request = request.into_inner();
134 let tx = ProvenTransaction::read_from_bytes(&request.transaction).map_err(|err| {
135 Status::invalid_argument(err.as_report_context("Invalid proven transaction"))
136 })?;
137 let inputs = request
138 .transaction_inputs
139 .ok_or(Status::invalid_argument("Missing transaction inputs"))?;
140 let inputs = TransactionInputs::read_from_bytes(&inputs).map_err(|err| {
141 Status::invalid_argument(err.as_report_context("Invalid transaction inputs"))
142 })?;
143
144 Result::<_, tonic::Status>::Ok((tx, inputs))
145 })?;
146
147 tracing::Span::current().set_attribute("transaction.id", tx.id());
148
149 let tx_info = validate_transaction(tx, inputs).await.map_err(|err| {
151 Status::invalid_argument(err.as_report_context("Invalid transaction"))
152 })?;
153
154 self.db
156 .transact("insert_transaction", move |conn| insert_transaction(conn, &tx_info))
157 .await
158 .map_err(|err| {
159 Status::internal(err.as_report_context("Failed to insert transaction"))
160 })?;
161 Ok(tonic::Response::new(()))
162 }
163
164 async fn sign_block(
166 &self,
167 request: tonic::Request<proto::blockchain::ProposedBlock>,
168 ) -> Result<tonic::Response<proto::blockchain::BlockSignature>, tonic::Status> {
169 let proposed_block = info_span!("deserialize").in_scope(|| {
170 let proposed_block_bytes = request.into_inner().proposed_block;
171
172 ProposedBlock::read_from_bytes(&proposed_block_bytes).map_err(|err| {
173 tonic::Status::invalid_argument(format!(
174 "Failed to deserialize proposed block: {err}",
175 ))
176 })
177 })?;
178
179 let signature =
181 validate_block(proposed_block, &self.signer, &self.db).await.map_err(|err| {
182 tonic::Status::invalid_argument(format!(
183 "Failed to validate block: {}",
184 err.as_report()
185 ))
186 })?;
187
188 info_span!("serialize").in_scope(|| {
190 let response = proto::blockchain::BlockSignature { signature: signature.to_bytes() };
191 Ok(tonic::Response::new(response))
192 })
193 }
194}