miden_node_utils/tracing/
grpc.rs

1/// Creates a [`tracing::Span`] based on RPC service and method name.
2macro_rules! rpc_span {
3    ($service:literal, $method:literal) => {
4        tracing::info_span!(
5            concat!($service, "/", $method),
6            rpc.service = $service,
7            rpc.method = $method
8        )
9    };
10}
11
12/// Represents an Otel compatible, traced Miden component.
13///
14/// Used to select an appropriate [`tracing::Span`] for the tonic server to use.
15#[derive(Copy, Clone)]
16pub enum TracedComponent {
17    Rpc,
18    BlockProducer,
19    StoreRpc,
20    StoreBlockProducer,
21    StoreNtxBuilder,
22    RemoteProver,
23    RemoteProverProxy,
24}
25
26/// Returns a [`trace_fn`](tonic::transport::server::Server) implementation for the RPC which
27/// adds open-telemetry information to the span.
28///
29/// Creates an `info` span following the open-telemetry standard: `{service}.rpc/{method}`.
30/// Additionally also pulls in remote tracing context which allows the server trace to be connected
31/// to the client's origin trace.
32pub fn traced_span_fn<T>(component: TracedComponent) -> fn(&http::Request<T>) -> tracing::Span {
33    match component {
34        TracedComponent::Rpc => rpc_trace_fn,
35        TracedComponent::BlockProducer => block_producer_trace_fn,
36        TracedComponent::StoreRpc => store_rpc_trace_fn,
37        TracedComponent::StoreBlockProducer => store_block_producer_trace_fn,
38        TracedComponent::StoreNtxBuilder => store_ntx_builder_trace_fn,
39        TracedComponent::RemoteProver => remote_prover_trace_fn,
40        TracedComponent::RemoteProverProxy => remote_prover_proxy_trace_fn,
41    }
42}
43
44fn rpc_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
45    let span = match request.uri().path().rsplit('/').next() {
46        Some("CheckNullifiers") => rpc_span!("rpc.rpc", "CheckNullifiers"),
47        Some("CheckNullifiersByPrefix") => rpc_span!("rpc.rpc", "CheckNullifiersByPrefix"),
48        Some("GetBlockHeaderByNumber") => rpc_span!("rpc.rpc", "GetBlockHeaderByNumber"),
49        Some("SyncStorageMaps") => rpc_span!("rpc.rpc", "SyncStorageMaps"),
50        Some("SyncAccountVault") => rpc_span!("rpc.rpc", "SyncAccountVault"),
51        Some("SyncState") => rpc_span!("rpc.rpc", "SyncState"),
52        Some("SyncNotes") => rpc_span!("rpc.rpc", "SyncNotes"),
53        Some("GetNotesById") => rpc_span!("rpc.rpc", "GetNotesById"),
54        Some("SubmitProvenTransaction") => rpc_span!("rpc.rpc", "SubmitProvenTransaction"),
55        Some("GetAccountDetails") => rpc_span!("rpc.rpc", "GetAccountDetails"),
56        Some("GetBlockByNumber") => rpc_span!("rpc.rpc", "GetBlockByNumber"),
57        Some("GetAccountProofs") => rpc_span!("rpc.rpc", "GetAccountProofs"),
58        Some("Status") => rpc_span!("rpc.rpc", "Status"),
59        _ => rpc_span!("rpc.rpc", "Unknown"),
60    };
61    add_network_attributes(span, request)
62}
63
64fn block_producer_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
65    let span = match request.uri().path().rsplit('/').next() {
66        Some("SubmitProvenTransaction") => {
67            rpc_span!("block-producer.rpc", "SubmitProvenTransaction")
68        },
69        Some("Status") => rpc_span!("block-producer.rpc", "Status"),
70        _ => {
71            rpc_span!("block-producer.rpc", "Unknown")
72        },
73    };
74
75    let span = add_otel_span_attributes(span, request);
76    add_network_attributes(span, request)
77}
78
79fn store_rpc_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
80    let method = request.uri().path().rsplit('/').next().unwrap_or("Unknown");
81    let span = match method {
82        "CheckNullifiers" => rpc_span!("store.rpc.rpc", "CheckNullifiers"),
83        "CheckNullifiersByPrefix" => rpc_span!("store.rpc.rpc", "CheckNullifiersByPrefix"),
84        "GetAccountDetails" => rpc_span!("store.rpc.rpc", "GetAccountDetails"),
85        "GetAccountProofs" => rpc_span!("store.rpc.rpc", "GetAccountProofs"),
86        "GetBlockByNumber" => rpc_span!("store.rpc.rpc", "GetBlockByNumber"),
87        "GetBlockHeaderByNumber" => rpc_span!("store.rpc.rpc", "GetBlockHeaderByNumber"),
88        "GetNotesById" => rpc_span!("store.rpc.rpc", "GetNotesById"),
89        "SyncNotes" => rpc_span!("store.rpc.rpc", "SyncNotes"),
90        "SyncState" => rpc_span!("store.rpc.rpc", "SyncState"),
91        "Status" => rpc_span!("store.rpc.rpc", "Status"),
92        _ => rpc_span!("store.rpc.rpc", "Unknown"),
93    };
94
95    let span = add_otel_span_attributes(span, request);
96    add_network_attributes(span, request)
97}
98
99fn store_block_producer_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
100    let method = request.uri().path().rsplit('/').next().unwrap_or("Unknown");
101    let span = match method {
102        "ApplyBlock" => rpc_span!("store.block-producer.rpc", "ApplyBlock"),
103        "GetBlockHeaderByNumber" => rpc_span!("store.block-producer.rpc", "GetBlockHeaderByNumber"),
104        "GetBlockInputs" => rpc_span!("store.block-producer.rpc", "GetBlockInputs"),
105        "GetBatchInputs" => rpc_span!("store.block-producer.rpc", "GetBatchInputs"),
106        "GetTransactionInputs" => rpc_span!("store.block-producer.rpc", "GetTransactionInputs"),
107        _ => rpc_span!("store.block-producer.rpc", "Unknown"),
108    };
109
110    let span = add_otel_span_attributes(span, request);
111    add_network_attributes(span, request)
112}
113
114fn store_ntx_builder_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
115    let method = request.uri().path().rsplit('/').next().unwrap_or("Unknown");
116    let span = match method {
117        "GetBlockHeaderByNumber" => rpc_span!("store.ntx-builder.rpc", "GetBlockHeaderByNumber"),
118        "GetUnconsumedNetworkNotes" => {
119            rpc_span!("store.ntx-builder.rpc", "GetUnconsumedNetworkNotes")
120        },
121        "GetCurrentBlockchainData" => {
122            rpc_span!("store.ntx-builder.rpc", "GetCurrentBlockchainData")
123        },
124        "GetNetworkAccountDetailsByPrefix" => {
125            rpc_span!("store.ntx-builder.rpc", "GetNetworkAccountDetailsByPrefix")
126        },
127        _ => rpc_span!("store.ntx-builder.rpc", "Unknown"),
128    };
129
130    let span = add_otel_span_attributes(span, request);
131    add_network_attributes(span, request)
132}
133
134fn remote_prover_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
135    let method = request.uri().path().rsplit('/').next().unwrap_or("Unknown");
136    let span = match method {
137        "Prove" => rpc_span!("remote-prover.rpc", "Prove"),
138        "Status" => rpc_span!("remote-prover.rpc", "Status"),
139        _ => rpc_span!("remote-prover.rpc", "Unknown"),
140    };
141
142    let span = add_otel_span_attributes(span, request);
143    add_network_attributes(span, request)
144}
145
146fn remote_prover_proxy_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
147    let method = request.uri().path().rsplit('/').next().unwrap_or("Unknown");
148    let span = if method == "Status" {
149        rpc_span!("remote-prover-proxy.rpc", "Status")
150    } else {
151        rpc_span!("remote-prover-proxy.rpc", "Unknown")
152    };
153
154    let span = add_otel_span_attributes(span, request);
155    add_network_attributes(span, request)
156}
157
158/// Adds remote tracing context to the span.
159///
160/// Could be expanded in the future by adding in more open-telemetry properties.
161fn add_otel_span_attributes<T>(span: tracing::Span, request: &http::Request<T>) -> tracing::Span {
162    // Pull the open-telemetry parent context using the HTTP extractor. We could make a more
163    // generic gRPC extractor by utilising the gRPC metadata. However that
164    //     (a) requires cloning headers,
165    //     (b) we would have to write this ourselves, and
166    //     (c) gRPC metadata is transferred using HTTP headers in any case.
167    let otel_ctx = opentelemetry::global::get_text_map_propagator(|propagator| {
168        propagator.extract(&MetadataExtractor(&tonic::metadata::MetadataMap::from_headers(
169            request.headers().clone(),
170        )))
171    });
172    tracing_opentelemetry::OpenTelemetrySpanExt::set_parent(&span, otel_ctx);
173
174    span
175}
176
177/// Adds various network attributes to the span, including remote address and port.
178///
179/// See [server attributes](https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#server-attributes).
180fn add_network_attributes<T>(span: tracing::Span, request: &http::Request<T>) -> tracing::Span {
181    use super::OpenTelemetrySpanExt;
182    // Set HTTP attributes.
183    span.set_attribute("rpc.system", "grpc");
184    if let Some(host) = request.uri().host() {
185        span.set_attribute("server.address", host);
186    }
187    if let Some(host_port) = request.uri().port() {
188        span.set_attribute("server.port", host_port.as_u16());
189    }
190    let remote_addr = request
191        .extensions()
192        .get::<tonic::transport::server::TcpConnectInfo>()
193        .and_then(tonic::transport::server::TcpConnectInfo::remote_addr);
194    if let Some(addr) = remote_addr {
195        span.set_attribute("client.address", addr.ip());
196        span.set_attribute("client.port", addr.port());
197        span.set_attribute("network.peer.address", addr.ip());
198        span.set_attribute("network.peer.port", addr.port());
199        span.set_attribute("network.transport", "tcp");
200        match addr.ip() {
201            std::net::IpAddr::V4(_) => span.set_attribute("network.type", "ipv4"),
202            std::net::IpAddr::V6(_) => span.set_attribute("network.type", "ipv6"),
203        }
204    }
205
206    span
207}
208
209/// Injects open-telemetry remote context into traces.
210#[derive(Copy, Clone)]
211pub struct OtelInterceptor;
212
213impl tonic::service::Interceptor for OtelInterceptor {
214    fn call(
215        &mut self,
216        mut request: tonic::Request<()>,
217    ) -> Result<tonic::Request<()>, tonic::Status> {
218        use tracing_opentelemetry::OpenTelemetrySpanExt;
219        let ctx = tracing::Span::current().context();
220        opentelemetry::global::get_text_map_propagator(|propagator| {
221            propagator.inject_context(&ctx, &mut MetadataInjector(request.metadata_mut()));
222        });
223
224        Ok(request)
225    }
226}
227
228struct MetadataExtractor<'a>(&'a tonic::metadata::MetadataMap);
229impl opentelemetry::propagation::Extractor for MetadataExtractor<'_> {
230    /// Get a value for a key from the `MetadataMap`.  If the value can't be converted to &str,
231    /// returns None
232    fn get(&self, key: &str) -> Option<&str> {
233        self.0.get(key).and_then(|metadata| metadata.to_str().ok())
234    }
235
236    /// Collect all the keys from the `MetadataMap`.
237    fn keys(&self) -> Vec<&str> {
238        self.0
239            .keys()
240            .map(|key| match key {
241                tonic::metadata::KeyRef::Ascii(v) => v.as_str(),
242                tonic::metadata::KeyRef::Binary(v) => v.as_str(),
243            })
244            .collect::<Vec<_>>()
245    }
246}
247
248struct MetadataInjector<'a>(&'a mut tonic::metadata::MetadataMap);
249impl opentelemetry::propagation::Injector for MetadataInjector<'_> {
250    /// Set a key and value in the `MetadataMap`.  Does nothing if the key or value are not valid
251    /// inputs
252    fn set(&mut self, key: &str, value: String) {
253        if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.as_bytes())
254            && let Ok(val) = tonic::metadata::MetadataValue::try_from(&value)
255        {
256            self.0.insert(key, val);
257        }
258    }
259}