linera_rpc/grpc/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4mod client;
5mod conversions;
6mod node_provider;
7pub mod pool;
8#[cfg(with_server)]
9mod server;
10pub mod transport;
11
12pub use client::*;
13pub use conversions::*;
14pub use node_provider::*;
15#[cfg(with_server)]
16pub use server::*;
17
18pub mod api {
19    tonic::include_proto!("rpc.v1");
20}
21
22#[derive(thiserror::Error, Debug)]
23pub enum GrpcError {
24    #[error("failed to connect to address: {0}")]
25    ConnectionFailed(#[from] transport::Error),
26
27    #[error("failed to execute task to completion: {0}")]
28    Join(#[from] futures::channel::oneshot::Canceled),
29
30    #[error("failed to parse socket address: {0}")]
31    SocketAddr(#[from] std::net::AddrParseError),
32
33    #[cfg(with_server)]
34    #[error(transparent)]
35    Reflection(#[from] tonic_reflection::server::Error),
36}
37
38const MEBIBYTE: usize = 1024 * 1024;
39pub const GRPC_MAX_MESSAGE_SIZE: usize = 16 * MEBIBYTE;
40
41/// Limit of gRPC message size up to which we will try to populate with data when estimating.
42/// We leave 30% of buffer for the rest of the message and potential underestimation.
43pub const GRPC_CHUNKED_MESSAGE_FILL_LIMIT: usize = GRPC_MAX_MESSAGE_SIZE * 7 / 10;
44
45/// Prometheus label for the gRPC method name.
46pub const METHOD_NAME_LABEL: &str = "method_name";
47
48/// Prometheus label for distinguishing organic vs synthetic (benchmark) traffic.
49pub const TRAFFIC_TYPE_LABEL: &str = "traffic_type";
50
51/// Prometheus label for the error variant name, e.g. `"WorkerError::UnexpectedBlockHeight"`.
52pub const ERROR_TYPE_LABEL: &str = "error_type";
53
54/// Extracts the gRPC method name from a request URI path.
55///
56/// gRPC paths have the form `/{package}.{Service}/{Method}` — the first segment
57/// always contains a dot. Non-gRPC requests (health checks, bot probes, etc.) return
58/// `"non_grpc"` to prevent unbounded label cardinality.
59pub fn extract_grpc_method_name(path: &str) -> &str {
60    let parts: Vec<&str> = path.splitn(3, '/').collect();
61    if parts.len() == 3 && parts[1].contains('.') {
62        parts[2]
63    } else {
64        "non_grpc"
65    }
66}
67
68#[cfg(test)]
69mod method_name_tests {
70    use super::*;
71
72    #[test]
73    fn grpc_unary_method() {
74        assert_eq!(
75            extract_grpc_method_name("/rpc.v1.ValidatorNode/HandleBlockProposal"),
76            "HandleBlockProposal"
77        );
78    }
79
80    #[test]
81    fn grpc_streaming_method() {
82        assert_eq!(
83            extract_grpc_method_name("/rpc.v1.ValidatorNode/SubscribeToNotifications"),
84            "SubscribeToNotifications"
85        );
86    }
87
88    #[test]
89    fn health_check_path() {
90        assert_eq!(
91            extract_grpc_method_name("/grpc.health.v1.Health/Check"),
92            "Check"
93        );
94    }
95
96    #[test]
97    fn non_grpc_root_path() {
98        assert_eq!(extract_grpc_method_name("/"), "non_grpc");
99    }
100
101    #[test]
102    fn non_grpc_plain_path() {
103        assert_eq!(extract_grpc_method_name("/healthz"), "non_grpc");
104    }
105
106    #[test]
107    fn non_grpc_no_dot_in_service() {
108        assert_eq!(extract_grpc_method_name("/NoDotService/Method"), "non_grpc");
109    }
110
111    #[test]
112    fn empty_path() {
113        assert_eq!(extract_grpc_method_name(""), "non_grpc");
114    }
115}