Skip to main content

miden_node_validator/server/
mod.rs

1use 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
30// VALIDATOR
31// ================================================================================
32
33/// The handle into running the gRPC validator server.
34///
35/// Facilitates the running of the gRPC server which implements the validator API.
36pub struct Validator {
37    /// The address of the validator component.
38    pub address: SocketAddr,
39    /// Server-side timeout for an individual gRPC request.
40    ///
41    /// If the handler takes longer than this duration, the server cancels the call.
42    pub grpc_timeout: Duration,
43
44    /// The signer used to sign blocks.
45    pub signer: ValidatorSigner,
46
47    /// The data directory for the validator component's database files.
48    pub data_directory: PathBuf,
49}
50
51impl Validator {
52    /// Serves the validator RPC API.
53    ///
54    /// Executes in place (i.e. not spawned) and will run indefinitely until a fatal error is
55    /// encountered.
56    pub async fn serve(self) -> anyhow::Result<()> {
57        tracing::info!(target: COMPONENT, endpoint=?self.address, "Initializing server");
58
59        // Initialize database connection.
60        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        // This is currently required for postman to work properly because
74        // it doesn't support the new version yet.
75        //
76        // See: <https://github.com/postmanlabs/postman-app-support/issues/13120>.
77        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        // Build the gRPC server with the API service and trace layer.
83        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
96// VALIDATOR SERVER
97// ================================================================================
98
99/// The underlying implementation of the gRPC validator server.
100///
101/// Implements the gRPC API for the validator.
102struct 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    /// Returns the status of the validator.
116    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    /// Receives a proven transaction, then validates and stores it.
127    #[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        // Validate the transaction.
150        let tx_info = validate_transaction(tx, inputs).await.map_err(|err| {
151            Status::invalid_argument(err.as_report_context("Invalid transaction"))
152        })?;
153
154        // Store the validated transaction.
155        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    /// Validates a proposed block and returns the block header and body.
165    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        // Validate the block.
180        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        // Send the signature.
189        info_span!("serialize").in_scope(|| {
190            let response = proto::blockchain::BlockSignature { signature: signature.to_bytes() };
191            Ok(tonic::Response::new(response))
192        })
193    }
194}