Skip to main content

starfish_config/
parameters.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{path::PathBuf, time::Duration};
6
7use serde::{Deserialize, Serialize};
8
9/// Operational configurations of a consensus authority.
10///
11/// All fields should tolerate inconsistencies among authorities, without
12/// affecting safety of the protocol. Otherwise, they need to be part of IOTA
13/// protocol config or epoch state on-chain.
14///
15/// NOTE: fields with default values are specified in the serde default
16/// functions. Most operators should not need to specify any field, except
17/// db_path.
18#[derive(Clone, Debug, Deserialize, Serialize)]
19pub struct Parameters {
20    /// Path to consensus DB for this epoch. Required when initializing
21    /// consensus. This is calculated based on user configuration for base
22    /// directory.
23    #[serde(skip)]
24    pub db_path: PathBuf,
25
26    /// Time to wait for parent round leader before sealing a block, from when
27    /// parent round has a quorum.
28    #[serde(default = "Parameters::default_leader_timeout")]
29    pub leader_timeout: Duration,
30
31    /// Sustained spacing between own blocks: long-run production never exceeds
32    /// one block per `min_block_delay`. This avoids generating too many rounds
33    /// when latency is low. This is especially necessary for tests running
34    /// locally. If setting a non-default value, it should be set low enough
35    /// to avoid reducing round rate and increasing latency in realistic and
36    /// distributed configurations.
37    #[serde(default = "Parameters::default_min_block_delay")]
38    pub min_block_delay: Duration,
39
40    /// Soft counterpart of `leader_timeout`: after this duration we are
41    /// willing to propose a block even without a strong-vote quorum, to avoid
42    /// liveness stalls when leader data is slow to propagate. Fires earlier
43    /// than `leader_timeout` and does not force block creation on its own.
44    #[serde(default = "Parameters::default_soft_leader_timeout")]
45    pub soft_leader_timeout: Duration,
46
47    /// Window bounding own block production together with `min_block_delay`:
48    /// idle time accrues budget for bursts of up to `block_rate_window /
49    /// min_block_delay` back-to-back blocks, letting a validator that fell
50    /// behind catch up on rounds instead of skipping them. Set at or below
51    /// `min_block_delay` to disable bursting (fixed spacing between blocks).
52    #[serde(default = "Parameters::default_block_rate_window")]
53    pub block_rate_window: Duration,
54
55    /// Number of block headers to fetch per commit sync request.
56    #[serde(default = "Parameters::default_max_headers_per_commit_sync_fetch")]
57    pub max_headers_per_commit_sync_fetch: usize,
58
59    /// Number of transactions to fetch per commit sync request.
60    #[serde(default = "Parameters::default_max_transactions_per_commit_sync_fetch")]
61    pub max_transactions_per_commit_sync_fetch: usize,
62
63    /// Number of block headers to fetch per header sync (periodic or live)
64    /// request.
65    #[serde(default = "Parameters::default_max_headers_per_header_sync_fetch")]
66    pub max_headers_per_header_sync_fetch: usize,
67
68    /// Number of transactions to fetch per transaction sync request.
69    #[serde(default = "Parameters::default_max_transactions_per_transaction_sync_fetch")]
70    pub max_transactions_per_transaction_sync_fetch: usize,
71
72    /// Time to wait during node start up until the node has synced the last
73    /// proposed block via the network peers. When set to `0` the sync
74    /// mechanism is disabled. This property is meant to be used for amnesia
75    /// recovery.
76    #[serde(default = "Parameters::default_sync_last_known_own_block_timeout")]
77    pub sync_last_known_own_block_timeout: Duration,
78
79    /// The number of rounds of blocks to be kept in the Dag state cache per
80    /// authority. The larger the number the more the blocks that will be
81    /// kept in memory allowing minimising any potential disk access.
82    /// Value should be at minimum 50 rounds to ensure node performance, but
83    /// being too large can be expensive in memory usage.
84    #[serde(default = "Parameters::default_dag_state_cached_rounds")]
85    pub dag_state_cached_rounds: u32,
86
87    /// Rounds a header from a far-future-bounded source may lead the locally
88    /// accepted frontier, in addition to `dag_state_cached_rounds`, before it
89    /// is dropped as too far ahead to connect.
90    #[serde(default = "Parameters::default_peer_round_ahead_margin")]
91    pub peer_round_ahead_margin: u32,
92
93    // Number of authorities commit syncer fetches in parallel.
94    // Both commits in a range and blocks referenced by the commits are fetched per authority.
95    #[serde(default = "Parameters::default_commit_sync_parallel_fetches")]
96    pub commit_sync_parallel_fetches: usize,
97
98    // Number of commits to fetch in a batch, also the maximum number of commits returned per
99    // fetch. If this value is set too small, fetching becomes inefficient.
100    // If this value is set too large, it can result in load imbalance and stragglers.
101    #[serde(default = "Parameters::default_commit_sync_batch_size")]
102    pub commit_sync_batch_size: u32,
103
104    // This affects the maximum number of commit batches being fetched, and those fetched but not
105    // processed as consensus output, before throttling of outgoing commit fetches starts.
106    #[serde(default = "Parameters::default_commit_sync_batches_ahead")]
107    pub commit_sync_batches_ahead: usize,
108
109    /// Maximum number of commits scanned and replayed per batch during
110    /// recovery, bounding peak memory when a large unprocessed range is
111    /// replayed at startup.
112    #[serde(default = "Parameters::default_commit_recovery_batch_size")]
113    pub commit_recovery_batch_size: u32,
114
115    /// Maximum number of headers to be included in a bundle. Headers exceeding
116    /// the max allowed limit will be truncated.
117    #[serde(default = "Parameters::default_max_headers_per_bundle")]
118    pub max_headers_per_bundle: usize,
119
120    /// Maximum number of transaction shards to be included in a bundle. Shards
121    /// exceeding the max allowed limit will be truncated.
122    #[serde(default = "Parameters::default_max_shards_per_bundle")]
123    pub max_shards_per_bundle: usize,
124
125    /// Tonic network settings.
126    #[serde(default = "TonicParameters::default")]
127    pub tonic: TonicParameters,
128
129    // Number of commits to fetch in a batch for fast commit syncer, also the maximum number of
130    // commits returned per fetch. If this value is set too small, fetching becomes
131    // inefficient. If this value is set too large, it can result in load imbalance and
132    // stragglers.
133    #[serde(default = "Parameters::default_fast_commit_sync_batch_size")]
134    pub fast_commit_sync_batch_size: u32,
135
136    // Gap threshold for switching between commit syncers. When the gap between quorum and local
137    // commit index is larger than this threshold, FastCommitSyncer fetches. Otherwise,
138    // CommitSyncer fetches.
139    #[serde(default = "Parameters::default_commit_sync_gap_threshold")]
140    pub commit_sync_gap_threshold: u32,
141
142    /// Enable FastCommitSyncer for faster recovery from large commit gaps.
143    /// Enabled by default; operators can disable it locally if bugs are
144    /// discovered.
145    #[serde(default = "Parameters::default_enable_fast_commit_syncer")]
146    pub enable_fast_commit_syncer: bool,
147
148    /// Ask commit-sync peers that have voted for the end of the requested
149    /// range before those that have not, since a vote means the peer has
150    /// solidified every commit in the range. Peers without an observed vote
151    /// are ordered behind; each fetch round tries a bounded number of peers,
152    /// so on a committee larger than that bound they can stay outside the
153    /// round until their votes are observed. Enabled by default; disabling it
154    /// restores a plain uniform order.
155    #[serde(default = "Parameters::default_enable_commit_sync_peer_selection_by_commit_votes")]
156    pub enable_commit_sync_peer_selection_by_commit_votes: bool,
157
158    /// Enable adaptive acknowledgment filtering for StarfishSpeed.
159    /// Local heuristic that drops acks for authorities persistently blamed
160    /// by recent strong-vote masks. Effective only when the protocol-level
161    /// `consensus_starfish_speed` flag is also on. Enabled by default;
162    /// operators can disable it locally without a protocol change.
163    #[serde(default = "Parameters::default_enable_starfish_speed_adaptive_acknowledgments")]
164    pub enable_starfish_speed_adaptive_acknowledgments: bool,
165
166    /// Prefer more responsive peers when the transactions synchronizer, the
167    /// commit syncer and the header synchronizer select peers to fetch from.
168    /// Responses are verified the same way regardless, so it cannot affect
169    /// safety. Enabled by default; disabling it restores the previous
170    /// selection: for the transactions synchronizer a uniform random order
171    /// that excludes the most recently failed peers (up to less than f+1 by
172    /// stake), and for the commit syncer and the header synchronizer a uniform
173    /// random order.
174    #[serde(default = "Parameters::default_enable_peer_responsiveness_ranking")]
175    pub enable_peer_responsiveness_ranking: bool,
176
177    /// Port for the DAG visualizer gRPC server (localhost only).
178    /// When set, starts a debugging server for real-time DAG visualization.
179    /// Only has an effect when the `dag-visualizer` feature is compiled in.
180    /// Disabled by default (None).
181    #[serde(default)]
182    pub dag_visualizer_port: Option<u16>,
183}
184
185impl Parameters {
186    /// Threshold for the number of commits sent to the consumer but not yet
187    /// handled, above which commit producers (commit syncers, commit observer
188    /// recovery) pause to let the consumer catch up.
189    pub fn unhandled_commits_threshold(&self) -> u32 {
190        self.commit_sync_batch_size * (self.commit_sync_batches_ahead as u32)
191    }
192
193    pub(crate) fn default_leader_timeout() -> Duration {
194        Duration::from_millis(200)
195    }
196
197    pub(crate) fn default_min_block_delay() -> Duration {
198        if cfg!(msim) || std::env::var("__TEST_ONLY_CONSENSUS_USE_LONG_MIN_BLOCK_DELAY").is_ok() {
199            // Checkpoint building and execution cannot keep up with high commit rate in
200            // simtests, leading to long reconfiguration delays. This is because
201            // simtest is single threaded, and spending too much time in
202            // consensus can lead to starvation elsewhere.
203            Duration::from_millis(400)
204        } else if cfg!(test) {
205            // Avoid excessive CPU, data and logs in tests.
206            Duration::from_millis(250)
207        } else {
208            // For production, use min delay between block being set to 50ms, reducing the
209            // block rate to 20 blocks/sec
210            Duration::from_millis(50)
211        }
212    }
213
214    pub(crate) fn default_soft_leader_timeout() -> Duration {
215        Duration::from_millis(5)
216    }
217
218    pub(crate) fn default_block_rate_window() -> Duration {
219        Duration::from_secs(2)
220    }
221
222    /// Burst capacity: maximum number of own blocks within `block_rate_window`
223    /// (40 in production, 5 in msim, 8 in tests with the default window).
224    pub fn block_rate_burst(&self) -> u64 {
225        let interval_ms = self.min_block_delay.as_millis().max(1) as u64;
226        (self.block_rate_window.as_millis() as u64 / interval_ms).max(1)
227    }
228
229    /// Highest round a header from a far-future-bounded source may have,
230    /// relative to the accepted `frontier`, to still be close enough to
231    /// connect; headers above this are too far ahead and dropped.
232    pub fn far_future_round_ceiling(&self, frontier: u32) -> u32 {
233        frontier
234            .saturating_add(self.dag_state_cached_rounds)
235            .saturating_add(self.peer_round_ahead_margin)
236    }
237
238    /// Maximum number of block headers served per fetch request, depending on
239    /// whether the request comes from commit sync or the header synchronizer.
240    pub fn max_headers_per_fetch(&self, commit_sync: bool) -> usize {
241        if commit_sync {
242            self.max_headers_per_commit_sync_fetch
243        } else {
244            self.max_headers_per_header_sync_fetch
245        }
246    }
247
248    /// Validates local consensus parameters, rejecting zero values that can
249    /// lead to synchronization problems. Returns a description of the first
250    /// offending field.
251    pub fn validate(&self) -> Result<(), String> {
252        let positive_fields = [
253            (
254                "max_headers_per_commit_sync_fetch",
255                self.max_headers_per_commit_sync_fetch as u128,
256            ),
257            (
258                "max_transactions_per_commit_sync_fetch",
259                self.max_transactions_per_commit_sync_fetch as u128,
260            ),
261            (
262                "max_headers_per_header_sync_fetch",
263                self.max_headers_per_header_sync_fetch as u128,
264            ),
265            (
266                "max_transactions_per_transaction_sync_fetch",
267                self.max_transactions_per_transaction_sync_fetch as u128,
268            ),
269            (
270                "dag_state_cached_rounds",
271                self.dag_state_cached_rounds as u128,
272            ),
273            (
274                "commit_sync_parallel_fetches",
275                self.commit_sync_parallel_fetches as u128,
276            ),
277            (
278                "commit_sync_batch_size",
279                self.commit_sync_batch_size as u128,
280            ),
281            (
282                "commit_recovery_batch_size",
283                self.commit_recovery_batch_size as u128,
284            ),
285            (
286                "commit_sync_batches_ahead",
287                self.commit_sync_batches_ahead as u128,
288            ),
289            (
290                "max_headers_per_bundle",
291                self.max_headers_per_bundle as u128,
292            ),
293            ("max_shards_per_bundle", self.max_shards_per_bundle as u128),
294            (
295                "fast_commit_sync_batch_size",
296                self.fast_commit_sync_batch_size as u128,
297            ),
298            (
299                "tonic.connection_buffer_size",
300                self.tonic.connection_buffer_size as u128,
301            ),
302            (
303                "tonic.excessive_message_size",
304                self.tonic.excessive_message_size as u128,
305            ),
306            (
307                "tonic.message_size_limit",
308                self.tonic.message_size_limit as u128,
309            ),
310            (
311                "tonic.keepalive_interval",
312                self.tonic.keepalive_interval.as_nanos(),
313            ),
314        ];
315        for (name, value) in positive_fields {
316            if value == 0 {
317                return Err(format!("{name} must be positive"));
318            }
319        }
320        Ok(())
321    }
322
323    // Maximum number of block headers to fetch per commit sync request.
324    pub(crate) fn default_max_headers_per_commit_sync_fetch() -> usize {
325        if cfg!(msim) {
326            // Exercise hitting blocks per fetch limit.
327            10
328        } else {
329            1000
330        }
331    }
332
333    // Maximum number of transactions to fetch per commit sync request.
334    pub(crate) fn default_max_transactions_per_commit_sync_fetch() -> usize {
335        if cfg!(msim) {
336            // Exercise hitting transactions per fetch limit.
337            10
338        } else {
339            1000
340        }
341    }
342
343    // Maximum number of block headers to fetch per header sync (periodic or
344    // live) request.
345    pub(crate) fn default_max_headers_per_header_sync_fetch() -> usize {
346        if cfg!(msim) {
347            // Exercise hitting blocks per fetch limit.
348            10
349        } else {
350            // TODO: This might should match the value of block headers in the bundle.
351            100
352        }
353    }
354
355    // Maximum number of transactions to fetch per transaction sync request.
356    pub(crate) fn default_max_transactions_per_transaction_sync_fetch() -> usize {
357        if cfg!(msim) { 10 } else { 1000 }
358    }
359
360    pub(crate) fn default_sync_last_known_own_block_timeout() -> Duration {
361        if cfg!(msim) {
362            Duration::from_millis(500)
363        } else {
364            // Here we prioritise liveness over the complete de-risking of block
365            // equivocation. 5 seconds in the majority of cases should be good
366            // enough for this given a healthy network.
367            Duration::from_secs(5)
368        }
369    }
370
371    pub(crate) fn default_dag_state_cached_rounds() -> u32 {
372        if cfg!(msim) {
373            // Exercise reading blocks from store.
374            5
375        } else {
376            500
377        }
378    }
379
380    pub(crate) fn default_peer_round_ahead_margin() -> u32 {
381        1000
382    }
383
384    pub(crate) fn default_commit_sync_parallel_fetches() -> usize {
385        8
386    }
387
388    pub(crate) fn default_commit_sync_batch_size() -> u32 {
389        if cfg!(msim) {
390            // Exercise commit sync.
391            5
392        } else {
393            100
394        }
395    }
396
397    pub(crate) fn default_commit_recovery_batch_size() -> u32 {
398        if cfg!(msim) { 3 } else { 250 }
399    }
400
401    pub(crate) fn default_commit_sync_batches_ahead() -> usize {
402        // This is set to be a multiple of default commit_sync_parallel_fetches to allow
403        // fetching ahead, while keeping the total number of inflight fetches
404        // and unprocessed fetched commits limited.
405        32
406    }
407
408    pub(crate) fn default_max_headers_per_bundle() -> usize {
409        150
410    }
411
412    pub(crate) fn default_max_shards_per_bundle() -> usize {
413        150
414    }
415
416    pub(crate) fn default_fast_commit_sync_batch_size() -> u32 {
417        if cfg!(msim) {
418            // Exercise fast commit sync.
419            5
420        } else {
421            // Sized so that commit_sync_parallel_fetches ranges fit under the
422            // unhandled-commits threshold (8 x 400 <= 3200), letting fast sync
423            // actually run its fetches in parallel. The server chunks larger
424            // responses across multiple messages either way.
425            400
426        }
427    }
428
429    pub(crate) fn default_commit_sync_gap_threshold() -> u32 {
430        if cfg!(msim) {
431            // Use smaller threshold for testing.
432            10
433        } else {
434            // When gap > 1000, FastCommitSyncer is more efficient.
435            // When gap <= 1000, CommitSyncer handles incremental sync.
436            1000
437        }
438    }
439
440    pub(crate) fn default_enable_fast_commit_syncer() -> bool {
441        // Enabled by default. Operators can disable it locally if bugs are discovered,
442        // without waiting for a protocol upgrade.
443        true
444    }
445
446    pub(crate) fn default_enable_commit_sync_peer_selection_by_commit_votes() -> bool {
447        // Enabled by default. Ordering only, so it cannot diverge consensus.
448        true
449    }
450
451    pub(crate) fn default_enable_starfish_speed_adaptive_acknowledgments() -> bool {
452        true
453    }
454
455    pub(crate) fn default_enable_peer_responsiveness_ranking() -> bool {
456        true
457    }
458}
459
460impl Default for Parameters {
461    fn default() -> Self {
462        Self {
463            db_path: PathBuf::default(),
464            leader_timeout: Parameters::default_leader_timeout(),
465            min_block_delay: Parameters::default_min_block_delay(),
466            soft_leader_timeout: Parameters::default_soft_leader_timeout(),
467            block_rate_window: Parameters::default_block_rate_window(),
468            max_headers_per_commit_sync_fetch:
469                Parameters::default_max_headers_per_commit_sync_fetch(),
470            max_transactions_per_commit_sync_fetch:
471                Parameters::default_max_transactions_per_commit_sync_fetch(),
472            max_headers_per_header_sync_fetch:
473                Parameters::default_max_headers_per_header_sync_fetch(),
474            max_transactions_per_transaction_sync_fetch:
475                Parameters::default_max_transactions_per_transaction_sync_fetch(),
476            sync_last_known_own_block_timeout:
477                Parameters::default_sync_last_known_own_block_timeout(),
478            dag_state_cached_rounds: Parameters::default_dag_state_cached_rounds(),
479            peer_round_ahead_margin: Parameters::default_peer_round_ahead_margin(),
480            commit_sync_parallel_fetches: Parameters::default_commit_sync_parallel_fetches(),
481            commit_sync_batch_size: Parameters::default_commit_sync_batch_size(),
482            commit_sync_batches_ahead: Parameters::default_commit_sync_batches_ahead(),
483            commit_recovery_batch_size: Parameters::default_commit_recovery_batch_size(),
484            max_headers_per_bundle: Parameters::default_max_headers_per_bundle(),
485            max_shards_per_bundle: Parameters::default_max_shards_per_bundle(),
486            tonic: TonicParameters::default(),
487            fast_commit_sync_batch_size: Parameters::default_fast_commit_sync_batch_size(),
488            commit_sync_gap_threshold: Parameters::default_commit_sync_gap_threshold(),
489            enable_fast_commit_syncer: Parameters::default_enable_fast_commit_syncer(),
490            enable_commit_sync_peer_selection_by_commit_votes:
491                Parameters::default_enable_commit_sync_peer_selection_by_commit_votes(),
492            enable_starfish_speed_adaptive_acknowledgments:
493                Parameters::default_enable_starfish_speed_adaptive_acknowledgments(),
494            enable_peer_responsiveness_ranking:
495                Parameters::default_enable_peer_responsiveness_ranking(),
496            dag_visualizer_port: None,
497        }
498    }
499}
500
501#[derive(Clone, Debug, Deserialize, Serialize)]
502pub struct TonicParameters {
503    /// Keepalive interval and timeouts for both client and server.
504    ///
505    /// If unspecified, this will default to 5s.
506    #[serde(default = "TonicParameters::default_keepalive_interval")]
507    pub keepalive_interval: Duration,
508
509    /// Size of various per-connection buffers.
510    ///
511    /// If unspecified, this will default to 32MiB.
512    #[serde(default = "TonicParameters::default_connection_buffer_size")]
513    pub connection_buffer_size: usize,
514
515    /// Messages over this size threshold will increment a counter.
516    ///
517    /// If unspecified, this will default to 16MiB.
518    #[serde(default = "TonicParameters::default_excessive_message_size")]
519    pub excessive_message_size: usize,
520
521    /// Hard message size limit for both requests and responses.
522    /// This value is higher than strictly necessary, to allow overheads.
523    /// Message size targets and soft limits are computed based on this value.
524    ///
525    /// If unspecified, this will default to 1GiB.
526    #[serde(default = "TonicParameters::default_message_size_limit")]
527    pub message_size_limit: usize,
528
529    /// Maximum number of concurrent HTTP/2 streams a peer may open on a single
530    /// connection. Bounds per-connection request fan-out.
531    ///
532    /// If unspecified, this will default to 64. `0` disables the limit, leaving
533    /// the transport default.
534    #[serde(default = "TonicParameters::default_max_concurrent_streams")]
535    pub max_concurrent_streams: u32,
536
537    /// Server-side fallback deadline for requests that omit a `grpc-timeout`
538    /// header. The long-lived block-subscription stream is always exempt.
539    ///
540    /// If unspecified, this will default to 120s. A zero duration disables the
541    /// fallback deadline.
542    #[serde(default = "TonicParameters::default_request_timeout")]
543    pub request_timeout: Duration,
544
545    /// Hard size limit for inbound (decoded) requests. Consensus requests are
546    /// small (ref lists); large payloads belong to responses, bounded by
547    /// `message_size_limit`. A smaller inbound bound shrinks the memory a
548    /// single in-flight request can pin before its handler runs.
549    ///
550    /// If unspecified, this will default to 1MiB. `0` falls back to
551    /// `message_size_limit`.
552    #[serde(default = "TonicParameters::default_max_inbound_message_size")]
553    pub max_inbound_message_size: usize,
554
555    /// Per-peer, per-RPC admission caps for the inbound consensus server.
556    #[serde(default)]
557    pub admission: AdmissionParameters,
558
559    /// Deadline for receiving the request message that opens a block
560    /// subscription stream. A peer that opens the stream and withholds the
561    /// request is disconnected once it expires; the stream itself stays exempt
562    /// from `request_timeout`.
563    ///
564    /// If unspecified, this will default to 30s. A zero duration disables the
565    /// deadline.
566    #[serde(default = "TonicParameters::default_subscribe_request_timeout")]
567    pub subscribe_request_timeout: Duration,
568}
569
570impl TonicParameters {
571    fn default_keepalive_interval() -> Duration {
572        Duration::from_secs(5)
573    }
574
575    fn default_connection_buffer_size() -> usize {
576        32 << 20
577    }
578
579    fn default_excessive_message_size() -> usize {
580        16 << 20
581    }
582
583    fn default_message_size_limit() -> usize {
584        64 << 20
585    }
586
587    fn default_max_concurrent_streams() -> u32 {
588        64
589    }
590
591    fn default_request_timeout() -> Duration {
592        Duration::from_secs(120)
593    }
594
595    fn default_max_inbound_message_size() -> usize {
596        1 << 20
597    }
598
599    fn default_subscribe_request_timeout() -> Duration {
600        Duration::from_secs(30)
601    }
602}
603
604impl Default for TonicParameters {
605    fn default() -> Self {
606        Self {
607            keepalive_interval: TonicParameters::default_keepalive_interval(),
608            connection_buffer_size: TonicParameters::default_connection_buffer_size(),
609            excessive_message_size: TonicParameters::default_excessive_message_size(),
610            message_size_limit: TonicParameters::default_message_size_limit(),
611            max_concurrent_streams: TonicParameters::default_max_concurrent_streams(),
612            request_timeout: TonicParameters::default_request_timeout(),
613            max_inbound_message_size: TonicParameters::default_max_inbound_message_size(),
614            admission: AdmissionParameters::default(),
615            subscribe_request_timeout: TonicParameters::default_subscribe_request_timeout(),
616        }
617    }
618}
619
620/// Per-peer, per-RPC concurrency caps for the inbound consensus gRPC server.
621///
622/// Each cap bounds how many concurrent requests of one RPC group a single
623/// committee peer (keyed on its authenticated authority index) may have in
624/// flight; a peer cannot consume another peer's budget. These are local,
625/// non-protocol parameters — heterogeneous values across authorities are safe,
626/// so they can be rolled out and tuned per node.
627///
628/// The defaults are sized for ~100-validator committees and the local
629/// synchronizer fan-out toward one server. `0` disables admission for that
630/// group.
631#[derive(Clone, Debug, Deserialize, Serialize)]
632pub struct AdmissionParameters {
633    /// Max concurrent block-subscription streams per peer.
634    ///
635    /// If unspecified, this will default to 2.
636    #[serde(default = "AdmissionParameters::default_max_subscriptions_per_peer")]
637    pub max_subscriptions_per_peer: u32,
638
639    /// Max concurrent header fetches per peer
640    /// (`fetch_block_headers` + `fetch_latest_block_headers`).
641    ///
642    /// If unspecified, this will default to 32.
643    #[serde(default = "AdmissionParameters::default_max_header_fetches_per_peer")]
644    pub max_header_fetches_per_peer: u32,
645
646    /// Max concurrent transaction fetches per peer (`fetch_transactions`).
647    ///
648    /// If unspecified, this will default to 16.
649    #[serde(default = "AdmissionParameters::default_max_transaction_fetches_per_peer")]
650    pub max_transaction_fetches_per_peer: u32,
651
652    /// Max concurrent commit fetches per peer
653    /// (`fetch_commits` + `fetch_commits_and_transactions`).
654    ///
655    /// If unspecified, this will default to 8.
656    #[serde(default = "AdmissionParameters::default_max_commit_fetches_per_peer")]
657    pub max_commit_fetches_per_peer: u32,
658}
659
660impl AdmissionParameters {
661    fn default_max_subscriptions_per_peer() -> u32 {
662        2
663    }
664
665    fn default_max_header_fetches_per_peer() -> u32 {
666        32
667    }
668
669    fn default_max_transaction_fetches_per_peer() -> u32 {
670        16
671    }
672
673    fn default_max_commit_fetches_per_peer() -> u32 {
674        Parameters::default_commit_sync_parallel_fetches() as u32
675    }
676}
677
678impl Default for AdmissionParameters {
679    fn default() -> Self {
680        Self {
681            max_subscriptions_per_peer: AdmissionParameters::default_max_subscriptions_per_peer(),
682            max_header_fetches_per_peer: AdmissionParameters::default_max_header_fetches_per_peer(),
683            max_transaction_fetches_per_peer:
684                AdmissionParameters::default_max_transaction_fetches_per_peer(),
685            max_commit_fetches_per_peer: AdmissionParameters::default_max_commit_fetches_per_peer(),
686        }
687    }
688}