miden_node_proto/clients/
mod.rs1use std::collections::HashMap;
25use std::fmt::Write;
26use std::marker::PhantomData;
27use std::time::Duration;
28
29use anyhow::{Context, Result};
30use miden_node_utils::tracing::grpc::OtelInterceptor;
31use tonic::metadata::AsciiMetadataValue;
32use tonic::service::Interceptor;
33use tonic::service::interceptor::InterceptedService;
34use tonic::transport::{Channel, ClientTlsConfig, Endpoint};
35use tonic::{Request, Status};
36use url::Url;
37
38use crate::generated;
39
40#[derive(Default, Clone)]
45pub struct MetadataInterceptor {
46 metadata: HashMap<&'static str, AsciiMetadataValue>,
47}
48
49impl MetadataInterceptor {
50 pub fn with_accept_metadata(
54 mut self,
55 version: &str,
56 genesis: Option<&str>,
57 ) -> Result<Self, anyhow::Error> {
58 let mut accept_value = format!("application/vnd.miden; version={version}");
59 if let Some(genesis) = genesis {
60 write!(accept_value, "; genesis={genesis}")?;
61 }
62 self.metadata.insert("accept", AsciiMetadataValue::try_from(accept_value)?);
63 Ok(self)
64 }
65}
66#[derive(Clone)]
70pub struct OtelAndMetadataInterceptor {
71 otel: OtelInterceptor,
72 metadata: MetadataInterceptor,
73}
74
75impl OtelAndMetadataInterceptor {
76 pub fn new(otel: OtelInterceptor, metadata: MetadataInterceptor) -> Self {
77 Self { otel, metadata }
78 }
79}
80
81impl Interceptor for OtelAndMetadataInterceptor {
82 fn call(&mut self, request: Request<()>) -> Result<Request<()>, Status> {
83 let req = self.otel.call(request)?;
85 self.metadata.call(req)
86 }
87}
88
89impl Interceptor for MetadataInterceptor {
90 fn call(&mut self, request: Request<()>) -> Result<Request<()>, Status> {
91 let mut request = request;
92 for (key, value) in &self.metadata {
93 request.metadata_mut().insert(*key, value.clone());
94 }
95 Ok(request)
96 }
97}
98
99pub type RpcClient =
103 generated::rpc::api_client::ApiClient<InterceptedService<Channel, OtelAndMetadataInterceptor>>;
104pub type BlockProducerClient =
105 generated::block_producer::api_client::ApiClient<InterceptedService<Channel, OtelInterceptor>>;
106pub type StoreNtxBuilderClient = generated::ntx_builder_store::ntx_builder_client::NtxBuilderClient<
107 InterceptedService<Channel, OtelInterceptor>,
108>;
109pub type StoreBlockProducerClient =
110 generated::block_producer_store::block_producer_client::BlockProducerClient<
111 InterceptedService<Channel, OtelInterceptor>,
112 >;
113pub type StoreRpcClient =
114 generated::rpc_store::rpc_client::RpcClient<InterceptedService<Channel, OtelInterceptor>>;
115
116pub type RemoteProverProxyStatusClient =
117 generated::remote_prover::proxy_status_api_client::ProxyStatusApiClient<
118 InterceptedService<Channel, OtelInterceptor>,
119 >;
120
121pub type RemoteProverClient =
122 generated::remote_prover::api_client::ApiClient<InterceptedService<Channel, OtelInterceptor>>;
123
124pub struct ClientConfig {
132 pub metadata_version: Option<String>,
133 pub metadata_genesis: Option<String>,
134}
135
136pub trait GrpcClientBuilder {
141 type Service;
142
143 fn with_interceptor(channel: Channel, config: &ClientConfig) -> Self::Service;
144}
145
146#[derive(Copy, Clone, Debug)]
150pub struct Rpc;
151
152#[derive(Copy, Clone, Debug)]
153pub struct BlockProducer;
154
155#[derive(Copy, Clone, Debug)]
156pub struct StoreNtxBuilder;
157
158#[derive(Copy, Clone, Debug)]
159pub struct StoreBlockProducer;
160
161#[derive(Copy, Clone, Debug)]
162pub struct StoreRpc;
163
164#[derive(Copy, Clone, Debug)]
165pub struct RemoteProverProxy;
166
167impl GrpcClientBuilder for Rpc {
171 type Service = RpcClient;
172
173 fn with_interceptor(channel: Channel, config: &ClientConfig) -> Self::Service {
174 let mut metadata = MetadataInterceptor::default();
176 if let Some(version) = config.metadata_version.as_deref() {
177 metadata = metadata
178 .with_accept_metadata(version, config.metadata_genesis.as_deref())
179 .expect("Failed to create metadata interceptor");
180 }
181 let combined = OtelAndMetadataInterceptor::new(OtelInterceptor, metadata);
182 generated::rpc::api_client::ApiClient::with_interceptor(channel, combined)
183 }
184}
185
186impl GrpcClientBuilder for BlockProducer {
187 type Service = BlockProducerClient;
188
189 fn with_interceptor(channel: Channel, _config: &ClientConfig) -> Self::Service {
190 generated::block_producer::api_client::ApiClient::with_interceptor(channel, OtelInterceptor)
191 }
192}
193
194impl GrpcClientBuilder for StoreNtxBuilder {
195 type Service = StoreNtxBuilderClient;
196
197 fn with_interceptor(channel: Channel, _config: &ClientConfig) -> Self::Service {
198 generated::ntx_builder_store::ntx_builder_client::NtxBuilderClient::with_interceptor(
199 channel,
200 OtelInterceptor,
201 )
202 }
203}
204
205impl GrpcClientBuilder for StoreBlockProducer {
206 type Service = StoreBlockProducerClient;
207
208 fn with_interceptor(channel: Channel, _config: &ClientConfig) -> Self::Service {
209 generated::block_producer_store::block_producer_client::BlockProducerClient::with_interceptor(
210 channel,
211 OtelInterceptor,
212 )
213 }
214}
215
216impl GrpcClientBuilder for StoreRpc {
217 type Service = StoreRpcClient;
218
219 fn with_interceptor(channel: Channel, _config: &ClientConfig) -> Self::Service {
220 generated::rpc_store::rpc_client::RpcClient::with_interceptor(channel, OtelInterceptor)
221 }
222}
223
224impl GrpcClientBuilder for RemoteProverProxy {
225 type Service = RemoteProverProxyStatusClient;
226
227 fn with_interceptor(channel: Channel, _config: &ClientConfig) -> Self::Service {
228 generated::remote_prover::proxy_status_api_client::ProxyStatusApiClient::with_interceptor(
229 channel,
230 OtelInterceptor,
231 )
232 }
233}
234
235impl GrpcClientBuilder for RemoteProverClient {
236 type Service = RemoteProverClient;
237
238 fn with_interceptor(channel: Channel, _config: &ClientConfig) -> Self::Service {
239 generated::remote_prover::api_client::ApiClient::with_interceptor(channel, OtelInterceptor)
240 }
241}
242
243#[derive(Clone, Debug)]
270pub struct Builder<State> {
271 endpoint: Endpoint,
272 metadata_version: Option<String>,
273 metadata_genesis: Option<String>,
274 _state: PhantomData<State>,
275}
276
277#[derive(Copy, Clone, Debug)]
278pub struct WantsTls;
279#[derive(Copy, Clone, Debug)]
280pub struct WantsTimeout;
281#[derive(Copy, Clone, Debug)]
282pub struct WantsVersion;
283#[derive(Copy, Clone, Debug)]
284pub struct WantsGenesis;
285#[derive(Copy, Clone, Debug)]
286pub struct WantsConnection;
287
288impl<State> Builder<State> {
289 fn next_state<Next>(self) -> Builder<Next> {
291 Builder {
292 endpoint: self.endpoint,
293 metadata_version: self.metadata_version,
294 metadata_genesis: self.metadata_genesis,
295 _state: PhantomData::<Next>,
296 }
297 }
298}
299
300impl Builder<WantsTls> {
301 pub fn new(url: Url) -> Builder<WantsTls> {
304 let endpoint = Endpoint::from_shared(String::from(url))
305 .expect("Url type always results in valid endpoint");
306
307 Builder {
308 endpoint,
309 metadata_version: None,
310 metadata_genesis: None,
311 _state: PhantomData,
312 }
313 }
314
315 pub fn without_tls(self) -> Builder<WantsTimeout> {
317 self.next_state()
318 }
319
320 pub fn with_tls(mut self) -> Result<Builder<WantsTimeout>> {
322 self.endpoint = self
323 .endpoint
324 .tls_config(ClientTlsConfig::new().with_native_roots())
325 .context("Failed to configure TLS")?;
326
327 Ok(self.next_state())
328 }
329}
330
331impl Builder<WantsTimeout> {
332 pub fn without_timeout(self) -> Builder<WantsVersion> {
334 self.next_state()
335 }
336
337 pub fn with_timeout(mut self, duration: Duration) -> Builder<WantsVersion> {
339 self.endpoint = self.endpoint.timeout(duration);
340 self.next_state()
341 }
342}
343
344impl Builder<WantsVersion> {
345 pub fn without_metadata_version(mut self) -> Builder<WantsGenesis> {
347 self.metadata_version = None;
348 self.next_state()
349 }
350
351 pub fn with_metadata_version(mut self, version: String) -> Builder<WantsGenesis> {
353 self.metadata_version = Some(version);
354 self.next_state()
355 }
356}
357
358impl Builder<WantsGenesis> {
359 pub fn without_metadata_genesis(mut self) -> Builder<WantsConnection> {
361 self.metadata_genesis = None;
362 self.next_state()
363 }
364
365 pub fn with_metadata_genesis(mut self, genesis: String) -> Builder<WantsConnection> {
367 self.metadata_genesis = Some(genesis);
368 self.next_state()
369 }
370}
371
372impl Builder<WantsConnection> {
373 pub async fn connect<T>(self) -> Result<T::Service>
375 where
376 T: GrpcClientBuilder,
377 {
378 let channel = self.endpoint.connect().await?;
379 let cfg = ClientConfig {
380 metadata_version: self.metadata_version,
381 metadata_genesis: self.metadata_genesis,
382 };
383 Ok(T::with_interceptor(channel, &cfg))
384 }
385
386 pub fn connect_lazy<T>(self) -> T::Service
388 where
389 T: GrpcClientBuilder,
390 {
391 let channel = self.endpoint.connect_lazy();
392 let cfg = ClientConfig {
393 metadata_version: self.metadata_version,
394 metadata_genesis: self.metadata_genesis,
395 };
396 T::with_interceptor(channel, &cfg)
397 }
398}