Skip to main content

mysten_network/request_log/
layer.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use prost_reflect::DescriptorPool;
5use tower::Layer;
6
7use super::GrpcRequestLog;
8
9/// Large enough for any Sui transaction payload (the protocol caps transactions well below this),
10/// and deliberately smaller than tonic's 4 MiB decode limit: the captured payload is re-emitted as
11/// a single JSON log line, so this also bounds log-line size and the memory buffered per request.
12/// Larger frames are skipped rather than buffered.
13const DEFAULT_MAX_CAPTURED_MESSAGE_SIZE: usize = 512 * 1024;
14
15/// Layer that applies [`GrpcRequestLog`] to a service.
16///
17/// See the [module docs](super) for how captures are enabled and collected.
18#[derive(Clone)]
19pub struct GrpcRequestLogLayer {
20    pool: DescriptorPool,
21    max_captured_message_size: usize,
22}
23
24impl GrpcRequestLogLayer {
25    pub fn new(pool: DescriptorPool) -> Self {
26        Self {
27            pool,
28            max_captured_message_size: DEFAULT_MAX_CAPTURED_MESSAGE_SIZE,
29        }
30    }
31
32    /// Build the descriptor pool from the same encoded `FileDescriptorSet`s the server registers
33    /// for gRPC reflection. Sets are decoded one at a time: files already added by an earlier set
34    /// are skipped (so well-known types appearing in several sets built with `--include_imports`
35    /// are fine), while duplicates *within* one set are malformed and error. A set may reference
36    /// files from the sets before it, so dependencies must come first.
37    pub fn from_encoded_file_descriptor_sets<'a>(
38        sets: impl IntoIterator<Item = &'a [u8]>,
39    ) -> Result<Self, prost_reflect::DescriptorError> {
40        // `DescriptorPool` indexes its symbol table with plain `std::collections::HashMap`s, seeded
41        // from `getrandom()`. Under `msim`, that syscall is only reliably intercepted (made
42        // deterministic) on macOS; on Linux it leaks real entropy into the simulated run, which
43        // desyncs the two-iteration determinism check the moment this pool is built at server
44        // startup. `nondeterministic!` moves the build onto an unintercepted thread so the leak is
45        // invisible to msim's tracked RNG log, the same fix used for `tempfile` and RocksDB
46        // elsewhere in this repo. Collect first so the closure moved onto that thread doesn't need
47        // a `Send` bound added to the public `impl IntoIterator` parameter.
48        let sets: Vec<&'a [u8]> = sets.into_iter().collect();
49        sui_macros::nondeterministic!({
50            let mut pool = DescriptorPool::new();
51            for set in sets {
52                pool.decode_file_descriptor_set(set)?;
53            }
54            Ok(Self::new(pool))
55        })
56    }
57
58    pub fn with_max_captured_message_size(mut self, bytes: usize) -> Self {
59        self.max_captured_message_size = bytes;
60        self
61    }
62}
63
64impl<S> Layer<S> for GrpcRequestLogLayer {
65    type Service = GrpcRequestLog<S>;
66
67    fn layer(&self, inner: S) -> Self::Service {
68        GrpcRequestLog::new(inner, self.pool.clone(), self.max_captured_message_size)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    /// The same file appearing in more than one set must be skipped, not error — servers register
77    /// well-known types alongside sets built with `--include_imports` that embed them again.
78    #[test]
79    fn duplicate_files_across_sets_are_skipped() {
80        GrpcRequestLogLayer::from_encoded_file_descriptor_sets([
81            tonic_health::pb::FILE_DESCRIPTOR_SET,
82            tonic_health::pb::FILE_DESCRIPTOR_SET,
83        ])
84        .unwrap();
85    }
86}