Skip to main content

sui_rpc_api/
client_protocol_version.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use sui_protocol_config::ProtocolVersion;
5use tonic::metadata::MetadataMap;
6
7/// Request header carrying the highest protocol version whose types the client can decode.
8pub const X_SUI_CLIENT_PROTOCOL_VERSION: &str = "x-sui-client-protocol-version";
9
10/// The highest protocol version the client that sent a request understands, if it said.
11///
12/// Self-reported, so it may be used to pick a response shape the client can decode but never for
13/// access control. `None` for clients that predate the header or send a malformed value.
14pub fn client_protocol_version(metadata: &MetadataMap) -> Option<ProtocolVersion> {
15    metadata
16        .get(X_SUI_CLIENT_PROTOCOL_VERSION)?
17        .to_str()
18        .ok()?
19        .parse()
20        .ok()
21        .map(ProtocolVersion::new)
22}
23
24/// How far past this binary's max protocol version a reported version is still recorded as
25/// itself. A client may know a few versions this node doesn't, but not many.
26const METRIC_VERSIONS_ABOVE_MAX: u64 = 20;
27
28/// The client's reported protocol version for metrics, or 0 (never a real protocol version) if
29/// the header is missing or invalid. The header is client-controlled, so versions above
30/// `ProtocolVersion::MAX_ALLOWED + METRIC_VERSIONS_ABOVE_MAX` count as invalid, which bounds the
31/// number of distinct values.
32pub(crate) fn client_protocol_version_for_metrics(metadata: &MetadataMap) -> u64 {
33    client_protocol_version(metadata)
34        .map(|v| v.as_u64())
35        .filter(|&v| v <= ProtocolVersion::MAX_ALLOWED.as_u64() + METRIC_VERSIONS_ABOVE_MAX)
36        .unwrap_or(0)
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    fn parse(value: Option<&'static str>) -> Option<ProtocolVersion> {
44        let mut metadata = MetadataMap::new();
45        if let Some(value) = value {
46            metadata.insert(X_SUI_CLIENT_PROTOCOL_VERSION, value.parse().unwrap());
47        }
48        client_protocol_version(&metadata)
49    }
50
51    #[test]
52    fn parses_header() {
53        assert_eq!(parse(Some("138")), Some(ProtocolVersion::new(138)));
54        assert_eq!(parse(None), None);
55        assert_eq!(parse(Some("")), None);
56        assert_eq!(parse(Some("-1")), None);
57        assert_eq!(parse(Some("1.2")), None);
58        assert_eq!(parse(Some("18446744073709551616")), None);
59    }
60
61    #[test]
62    fn metric_version_is_bounded() {
63        let version = |value: Option<String>| {
64            let mut metadata = MetadataMap::new();
65            if let Some(value) = value {
66                metadata.insert(X_SUI_CLIENT_PROTOCOL_VERSION, value.parse().unwrap());
67            }
68            client_protocol_version_for_metrics(&metadata)
69        };
70        let cap = ProtocolVersion::MAX_ALLOWED.as_u64() + METRIC_VERSIONS_ABOVE_MAX;
71
72        assert_eq!(version(None), 0);
73        assert_eq!(version(Some("abc".into())), 0);
74        assert_eq!(version(Some("137".into())), 137);
75        assert_eq!(version(Some(cap.to_string())), cap);
76        assert_eq!(version(Some((cap + 1).to_string())), 0);
77        assert_eq!(version(Some(u64::MAX.to_string())), 0);
78    }
79}