casper_node/components/consensus/highway_core/
state.rs

1mod block;
2mod index_panorama;
3mod panorama;
4mod params;
5mod tallies;
6mod unit;
7
8#[cfg(test)]
9pub(crate) mod tests;
10
11pub(crate) use params::Params;
12use quanta::Clock;
13use serde::{Deserialize, Serialize};
14
15pub(crate) use index_panorama::{IndexObservation, IndexPanorama};
16pub use panorama::{Observation, Panorama};
17pub(super) use unit::Unit;
18
19use std::{
20    borrow::Borrow,
21    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
22    iter,
23};
24
25use datasize::DataSize;
26use itertools::Itertools;
27use thiserror::Error;
28use tracing::{error, info, trace, warn};
29
30use casper_types::{TimeDiff, Timestamp};
31
32use crate::{
33    components::consensus::{
34        highway_core::{
35            endorsement::{Endorsement, SignedEndorsement},
36            evidence::Evidence,
37            highway::{Endorsements, HashedWireUnit, SignedWireUnit, WireUnit},
38            ENABLE_ENDORSEMENTS,
39        },
40        traits::Context,
41        utils::{ValidatorIndex, ValidatorMap, Weight},
42        LeaderSequence,
43    },
44    utils::ds,
45};
46use block::Block;
47use tallies::Tallies;
48
49// TODO: The restart mechanism only persists and loads our own latest unit, so that we don't
50// equivocate after a restart. It doesn't yet persist our latest endorsed units, so we could
51// accidentally endorse conflicting votes. Fix this and enable detecting conflicting
52// endorsements again.
53pub(super) const TODO_ENDORSEMENT_EVIDENCE_DISABLED: bool = true;
54
55/// Number of maximum-length rounds after which a validator counts as offline, if we haven't heard
56/// from them.
57const PING_TIMEOUT: u64 = 3;
58
59#[derive(Debug, Error, Eq, PartialEq, Clone)]
60pub(crate) enum UnitError {
61    #[error("The unit is a ballot but doesn't cite any block.")]
62    MissingBlock,
63    #[error("The panorama's length {} doesn't match the number of validators.", _0)]
64    PanoramaLength(usize),
65    #[error("The unit accuses its own creator as faulty.")]
66    FaultyCreator,
67    #[error("The panorama has a unit from {:?} in the slot for {:?}.", _0, _1)]
68    PanoramaIndex(ValidatorIndex, ValidatorIndex),
69    #[error("The panorama is missing units indirectly cited via {:?}.", _0)]
70    InconsistentPanorama(ValidatorIndex),
71    #[error("The unit contains the wrong sequence number.")]
72    SequenceNumber,
73    #[error("The unit's timestamp is older than a justification's.")]
74    Timestamps,
75    #[error("The creator is not a validator.")]
76    Creator,
77    #[error("The unit was created for a wrong instance ID.")]
78    InstanceId,
79    #[error("The signature is invalid.")]
80    Signature,
81    #[error("The round length has somehow changed within a round.")]
82    RoundLengthChangedWithinRound,
83    #[error("The round length is greater than the maximum allowed by the chainspec.")]
84    RoundLengthGreaterThanMaximum,
85    #[error("This would be the third unit in that round. Only two are allowed.")]
86    ThreeUnitsInRound,
87    #[error(
88        "A block must be the leader's ({:?}) first unit, at the beginning of the round.",
89        _0
90    )]
91    NonLeaderBlock(ValidatorIndex),
92    #[error("The unit is a block, but its parent is already a terminal block.")]
93    ValueAfterTerminalBlock,
94    #[error("The unit's creator is banned.")]
95    Banned,
96    #[error("The unit's endorsed units were not a superset of its justifications.")]
97    EndorsementsNotMonotonic,
98    #[error("The LNC rule was violated. Vote cited ({:?}) naively.", _0)]
99    LncNaiveCitation(ValidatorIndex),
100    #[error(
101        "Wire unit endorses hash but does not see it. Hash: {:?}; Wire unit: {:?}",
102        hash,
103        wire_unit
104    )]
105    EndorsedButUnseen { hash: String, wire_unit: String },
106}
107
108/// A reason for a validator to be marked as faulty.
109///
110/// The `Banned` state is fixed from the beginning and can't be replaced. However, `Indirect` can
111/// be replaced with `Direct` evidence, which has the same effect but doesn't rely on information
112/// from other consensus protocol instances.
113#[derive(Clone, DataSize, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
114pub enum Fault<C>
115where
116    C: Context,
117{
118    /// The validator was known to be malicious from the beginning. All their messages are
119    /// considered invalid in this Highway instance.
120    Banned,
121    /// We have direct evidence of the validator's fault.
122    Direct(Evidence<C>),
123    /// The validator is known to be faulty, but the evidence is not in this Highway instance.
124    Indirect,
125}
126
127impl<C: Context> Fault<C> {
128    /// Returns the evidence included in this `Fault`.
129    pub fn evidence(&self) -> Option<&Evidence<C>> {
130        match self {
131            Fault::Banned | Fault::Indirect => None,
132            Fault::Direct(ev) => Some(ev),
133        }
134    }
135}
136
137/// A passive instance of the Highway protocol, containing its local state.
138///
139/// Both observers and active validators must instantiate this, pass in all incoming vertices from
140/// peers, and use a [FinalityDetector](../finality_detector/struct.FinalityDetector.html) to
141/// determine the outcome of the consensus process.
142#[derive(Debug, Clone, DataSize, Serialize, Deserialize)]
143pub struct State<C>
144where
145    C: Context,
146{
147    /// The fixed parameters.
148    params: Params,
149    /// The validator's voting weights.
150    weights: ValidatorMap<Weight>,
151    /// The pseudorandom sequence of round leaders.
152    leader_sequence: LeaderSequence,
153    /// All units imported so far, by hash.
154    /// This is a downward closed set: A unit must only be added here once all of its dependencies
155    /// have been added as well, and it has been fully validated.
156    #[data_size(with = ds::hashmap_sample)]
157    units: HashMap<C::Hash, Unit<C>>,
158    /// All blocks, by hash. All block hashes are also unit hashes, but units that did not
159    /// introduce a new block don't have their own entry here.
160    #[data_size(with = ds::hashmap_sample)]
161    blocks: HashMap<C::Hash, Block<C>>,
162    /// List of faulty validators and their type of fault.
163    /// Every validator that has an equivocation in `units` must have an entry here, but there can
164    /// be additional entries for other kinds of faults.
165    faults: HashMap<ValidatorIndex, Fault<C>>,
166    /// The full panorama, corresponding to the complete protocol state.
167    /// This points to the latest unit of every honest validator.
168    panorama: Panorama<C>,
169    /// All currently endorsed units, by hash: units that have enough endorsements to be cited even
170    /// if they naively cite an equivocator.
171    #[data_size(with = ds::hashmap_sample)]
172    endorsements: HashMap<C::Hash, ValidatorMap<Option<C::Signature>>>,
173    /// Units that don't yet have 1/2 of stake endorsing them.
174    /// Signatures are stored in a map so that a single validator sending lots of signatures for
175    /// different units doesn't cause us to allocate a lot of memory.
176    #[data_size(with = ds::hashmap_sample)]
177    incomplete_endorsements: HashMap<C::Hash, BTreeMap<ValidatorIndex, C::Signature>>,
178    /// Timestamp of the last ping or unit we received from each validator.
179    pings: ValidatorMap<Timestamp>,
180    /// Clock to measure time spent in fork choice computation.
181    #[data_size(skip)] // Not implemented for Clock; probably negligible.
182    #[serde(skip, default)]
183    // Serialization is used by external tools only, which cannot make sense of `Clock`.
184    clock: Clock,
185}
186
187impl<C: Context> State<C> {
188    pub(crate) fn new<I, IB, IB2>(
189        weights: I,
190        params: Params,
191        banned: IB,
192        cannot_propose: IB2,
193    ) -> State<C>
194    where
195        I: IntoIterator,
196        I::Item: Borrow<Weight>,
197        IB: IntoIterator<Item = ValidatorIndex>,
198        IB2: IntoIterator<Item = ValidatorIndex>,
199    {
200        let weights = ValidatorMap::from(weights.into_iter().map(|w| *w.borrow()).collect_vec());
201        assert!(
202            !weights.is_empty(),
203            "cannot initialize Highway with no validators"
204        );
205        let mut panorama = Panorama::new(weights.len());
206        let faults: HashMap<_, _> = banned.into_iter().map(|idx| (idx, Fault::Banned)).collect();
207        for idx in faults.keys() {
208            assert!(
209                idx.0 < weights.len() as u32,
210                "invalid banned validator index"
211            );
212            panorama[*idx] = Observation::Faulty;
213        }
214        let mut can_propose: ValidatorMap<bool> = weights.iter().map(|_| true).collect();
215        for idx in cannot_propose {
216            assert!(
217                idx.0 < weights.len() as u32,
218                "invalid validator index for exclusion from leader sequence"
219            );
220            can_propose[idx] = false;
221        }
222        let leader_sequence = LeaderSequence::new(params.seed(), &weights, can_propose);
223        let pings = iter::repeat(params.start_timestamp())
224            .take(weights.len())
225            .collect();
226        State {
227            params,
228            weights,
229            leader_sequence,
230            units: HashMap::new(),
231            blocks: HashMap::new(),
232            faults,
233            panorama,
234            endorsements: HashMap::new(),
235            incomplete_endorsements: HashMap::new(),
236            pings,
237            clock: Clock::new(),
238        }
239    }
240
241    /// Returns the fixed parameters.
242    pub fn params(&self) -> &Params {
243        &self.params
244    }
245
246    /// Returns the number of validators.
247    pub fn validator_count(&self) -> usize {
248        self.weights.len()
249    }
250
251    /// Returns the `idx`th validator's voting weight.
252    pub fn weight(&self, idx: ValidatorIndex) -> Weight {
253        self.weights[idx]
254    }
255
256    /// Returns the map of validator weights.
257    pub fn weights(&self) -> &ValidatorMap<Weight> {
258        &self.weights
259    }
260
261    /// Returns the total weight of all validators marked faulty in this panorama.
262    pub fn faulty_weight_in(&self, panorama: &Panorama<C>) -> Weight {
263        panorama
264            .iter()
265            .zip(&self.weights)
266            .filter(|(obs, _)| **obs == Observation::Faulty)
267            .map(|(_, w)| *w)
268            .sum()
269    }
270
271    /// Returns the total weight of all known-faulty validators.
272    pub fn faulty_weight(&self) -> Weight {
273        self.faulty_weight_in(self.panorama())
274    }
275
276    /// Returns the sum of all validators' voting weights.
277    pub fn total_weight(&self) -> Weight {
278        self.leader_sequence.total_weight()
279    }
280
281    /// Returns evidence against validator nr. `idx`, if present.
282    pub fn maybe_evidence(&self, idx: ValidatorIndex) -> Option<&Evidence<C>> {
283        self.maybe_fault(idx).and_then(Fault::evidence)
284    }
285
286    /// Returns endorsements for `unit`, if any.
287    pub fn maybe_endorsements(&self, unit: &C::Hash) -> Option<Endorsements<C>> {
288        self.endorsements.get(unit).map(|signatures| Endorsements {
289            unit: *unit,
290            endorsers: signatures.iter_some().map(|(i, sig)| (i, *sig)).collect(),
291        })
292    }
293
294    /// Returns whether evidence against validator nr. `idx` is known.
295    pub fn has_evidence(&self, idx: ValidatorIndex) -> bool {
296        self.maybe_evidence(idx).is_some()
297    }
298
299    /// Returns whether we have all endorsements for `unit`.
300    pub fn has_all_endorsements<I: IntoIterator<Item = ValidatorIndex>>(
301        &self,
302        unit: &C::Hash,
303        v_ids: I,
304    ) -> bool {
305        if self.endorsements.contains_key(unit) {
306            true // We have enough endorsements for this unit.
307        } else if let Some(sigs) = self.incomplete_endorsements.get(unit) {
308            v_ids.into_iter().all(|v_id| sigs.contains_key(&v_id))
309        } else {
310            v_ids.into_iter().next().is_none()
311        }
312    }
313
314    /// Returns whether we have seen enough endorsements for the unit.
315    /// Unit is endorsed when it has endorsements from more than 50% of the validators (by weight).
316    pub fn is_endorsed(&self, hash: &C::Hash) -> bool {
317        self.endorsements.contains_key(hash)
318    }
319
320    /// Returns hash of unit that needs to be endorsed.
321    pub fn needs_endorsements(&self, unit: &SignedWireUnit<C>) -> Option<C::Hash> {
322        unit.wire_unit()
323            .endorsed
324            .iter()
325            .find(|hash| !self.endorsements.contains_key(hash))
326            .cloned()
327    }
328
329    /// Returns the timestamp of the last ping or unit received from the validator, or the start
330    /// timestamp if we haven't received anything yet.
331    pub fn last_seen(&self, idx: ValidatorIndex) -> Timestamp {
332        self.pings[idx]
333    }
334
335    /// Marks the given validator as faulty, unless it is already banned or we have direct evidence.
336    pub fn mark_faulty(&mut self, idx: ValidatorIndex) {
337        self.panorama[idx] = Observation::Faulty;
338        self.faults.entry(idx).or_insert(Fault::Indirect);
339    }
340
341    /// Returns the fault type of validator nr. `idx`, if it is known to be faulty.
342    pub fn maybe_fault(&self, idx: ValidatorIndex) -> Option<&Fault<C>> {
343        self.faults.get(&idx)
344    }
345
346    /// Returns whether validator nr. `idx` is known to be faulty.
347    pub fn is_faulty(&self, idx: ValidatorIndex) -> bool {
348        self.faults.contains_key(&idx)
349    }
350
351    /// Returns an iterator over all faulty validators.
352    pub fn faulty_validators(&self) -> impl Iterator<Item = ValidatorIndex> + '_ {
353        self.faults.keys().cloned()
354    }
355
356    /// Returns an iterator over latest unit hashes from honest validators.
357    pub fn iter_correct_hashes(&self) -> impl Iterator<Item = &C::Hash> {
358        self.panorama.iter_correct_hashes()
359    }
360
361    /// Returns the unit with the given hash, if present.
362    pub fn maybe_unit(&self, hash: &C::Hash) -> Option<&Unit<C>> {
363        self.units.get(hash)
364    }
365
366    /// Returns whether the unit with the given hash is known.
367    pub fn has_unit(&self, hash: &C::Hash) -> bool {
368        self.units.contains_key(hash)
369    }
370
371    /// Returns the unit with the given hash. Panics if not found.
372    pub fn unit(&self, hash: &C::Hash) -> &Unit<C> {
373        self.maybe_unit(hash).expect("unit hash must exist")
374    }
375
376    /// Returns the block contained in the unit with the given hash, if present.
377    pub fn maybe_block(&self, hash: &C::Hash) -> Option<&Block<C>> {
378        self.blocks.get(hash)
379    }
380
381    /// Returns the block contained in the unit with the given hash. Panics if not found.
382    pub fn block(&self, hash: &C::Hash) -> &Block<C> {
383        self.maybe_block(hash).expect("block hash must exist")
384    }
385
386    /// Returns the complete protocol state's latest panorama.
387    pub fn panorama(&self) -> &Panorama<C> {
388        &self.panorama
389    }
390
391    /// Returns the leader in the specified time slot.
392    ///
393    /// First the assignment is computed ignoring the `can_propose` flags. Only if the selected
394    /// leader's entry is `false`, the computation is repeated, this time with the flagged
395    /// validators excluded. This ensures that once the validator set has been decided, correct
396    /// validators' slots never get reassigned to someone else, even if after the fact someone is
397    /// excluded as a leader.
398    pub fn leader(&self, timestamp: Timestamp) -> ValidatorIndex {
399        self.leader_sequence.leader(timestamp.millis())
400    }
401
402    /// Adds the unit to the protocol state.
403    ///
404    /// The unit must be valid (see `validate_unit`), and its dependencies satisfied.
405    pub(crate) fn add_valid_unit(&mut self, swunit: SignedWireUnit<C>) {
406        let wunit = swunit.wire_unit();
407        let hash = swunit.hash();
408        if self.has_unit(&hash) {
409            warn!(%hash, "called add_valid_unit twice");
410            return;
411        }
412        let instance_id = wunit.instance_id;
413        let fork_choice = self.fork_choice(&wunit.panorama).cloned();
414        let (unit, maybe_value) = Unit::new(swunit, fork_choice.as_ref(), self);
415        if let Some(value) = maybe_value {
416            let block = Block::new(fork_choice, value, self);
417            self.blocks.insert(hash, block);
418        }
419        self.add_ping(unit.creator, unit.timestamp);
420        self.units.insert(hash, unit);
421
422        // Update the panorama.
423        let unit = self.unit(&hash);
424        let creator = unit.creator;
425        let new_obs = match (&self.panorama[creator], &unit.panorama[creator]) {
426            (Observation::Faulty, _) => Observation::Faulty,
427            (obs0, obs1) if obs0 == obs1 => Observation::Correct(hash),
428            (Observation::None, _) => panic!("missing creator's previous unit"),
429            (Observation::Correct(hash0), _) => {
430                // We have all dependencies of unit, but it does not cite hash0 as its predecessor,
431                // which is our latest known unit by the creator. It must therefore cite an older
432                // unit and so its sequence number must be at most the same as hash0. Hence it is
433                // an equivocation, and to prove that, we only need to provide the other unit with
434                // the same sequence number.
435                let prev0 = self.find_in_swimlane(hash0, unit.seq_number).unwrap();
436                let wunit0 = self.wire_unit(prev0, instance_id).unwrap();
437                let wunit1 = self.wire_unit(&hash, instance_id).unwrap();
438                self.add_evidence(Evidence::Equivocation(wunit0, wunit1));
439                Observation::Faulty
440            }
441        };
442        self.panorama[creator] = new_obs;
443    }
444
445    /// Adds direct evidence proving a validator to be faulty, unless that validators is already
446    /// banned or we already have other direct evidence. This must only be called with valid
447    /// evidence (see `Evidence::validate`). Returns `false` if the evidence was not added because
448    /// the perpetrator is banned or we already have evidence against them.
449    pub(crate) fn add_evidence(&mut self, evidence: Evidence<C>) -> bool {
450        if TODO_ENDORSEMENT_EVIDENCE_DISABLED && matches!(evidence, Evidence::Endorsements { .. }) {
451            return false;
452        }
453        let idx = evidence.perpetrator();
454        match self.faults.get(&idx) {
455            Some(&Fault::Banned | &Fault::Direct(_)) => return false,
456            None | Some(&Fault::Indirect) => (),
457        }
458        // TODO: Should use Display, not Debug!
459        trace!(?evidence, "marking validator #{} as faulty", idx.0);
460        self.faults.insert(idx, Fault::Direct(evidence));
461        self.panorama[idx] = Observation::Faulty;
462        true
463    }
464
465    /// Adds a set of endorsements to the state.
466    /// If, after adding, we have collected enough endorsements to consider unit _endorsed_,
467    /// it will be *upgraded* to fully endorsed.
468    ///
469    /// Endorsements must be validated before calling this: The endorsers must exist, the
470    /// signatures must be valid and the endorsed unit must be present in `self.units`.
471    pub(crate) fn add_endorsements(&mut self, endorsements: Endorsements<C>) {
472        let uhash = *endorsements.unit();
473        if self.endorsements.contains_key(&uhash) {
474            return; // We already have a sufficient number of endorsements.
475        }
476        info!("Received endorsements of {:?}", uhash);
477        self.incomplete_endorsements
478            .entry(uhash)
479            .or_default()
480            .extend(endorsements.endorsers);
481        let endorsed: Weight = self.incomplete_endorsements[&uhash]
482            .keys()
483            .map(|vidx| self.weight(*vidx))
484            .sum();
485        // Stake required to consider unit to be endorsed.
486        // TODO - remove `allow` once false positive ceases.
487        #[allow(clippy::arithmetic_side_effects)] // False positive on `/ 2`.
488        let threshold = self.total_weight() / 2;
489        if endorsed > threshold {
490            info!(%uhash, "Unit endorsed by at least 1/2 of validators.");
491            // Unwrap is safe: We created the map entry above.
492            let mut fully_endorsed = self.incomplete_endorsements.remove(&uhash).unwrap();
493            let endorsed_map = self
494                .weights()
495                .keys()
496                .map(|vidx| fully_endorsed.remove(&vidx))
497                .collect();
498            self.endorsements.insert(uhash, endorsed_map);
499        }
500    }
501
502    /// Returns whether this state already includes an endorsement of `uhash` by `vidx`.
503    pub fn has_endorsement(&self, uhash: &C::Hash, vidx: ValidatorIndex) -> bool {
504        self.endorsements
505            .get(uhash)
506            .map(|vmap| vmap[vidx].is_some())
507            .unwrap_or(false)
508            || self
509                .incomplete_endorsements
510                .get(uhash)
511                .map(|ends| ends.contains_key(&vidx))
512                .unwrap_or(false)
513    }
514
515    /// Updates `self.pings` with the given timestamp.
516    pub(crate) fn add_ping(&mut self, creator: ValidatorIndex, timestamp: Timestamp) {
517        self.pings[creator] = self.pings[creator].max(timestamp);
518    }
519
520    /// Returns `true` if the latest timestamp we have is older than the given timestamp.
521    pub fn has_ping(&self, creator: ValidatorIndex, timestamp: Timestamp) -> bool {
522        self.pings
523            .get(creator)
524            .is_some_and(|ping_time| *ping_time >= timestamp)
525    }
526
527    /// Returns whether the validator's latest unit or ping is at most `PING_TIMEOUT` maximum round
528    /// lengths old.
529    pub(crate) fn is_online(&self, vidx: ValidatorIndex, now: Timestamp) -> bool {
530        self.pings.has(vidx)
531            && self.pings[vidx]
532                .saturating_add(self.params.max_round_length().saturating_mul(PING_TIMEOUT))
533                >= now
534    }
535
536    /// Creates new `Evidence` if the new endorsements contain any that conflict with existing
537    /// ones.
538    ///
539    /// Endorsements must be validated before calling this: The endorsers must exist, the
540    /// signatures must be valid and the endorsed unit must be present in `self.units`.
541    pub fn find_conflicting_endorsements(
542        &self,
543        endorsements: &Endorsements<C>,
544        instance_id: &C::InstanceId,
545    ) -> Vec<Evidence<C>> {
546        if TODO_ENDORSEMENT_EVIDENCE_DISABLED {
547            return Vec::new();
548        }
549        let uhash = endorsements.unit();
550        let unit = self.unit(uhash);
551        if !self.has_evidence(unit.creator) {
552            return vec![]; // There are no equivocations, so endorsements cannot conflict.
553        }
554
555        // We are only interested in endorsements that we didn't know before and whose validators
556        // we don't already have evidence against.
557        let is_new_endorsement = |&&(vidx, _): &&(ValidatorIndex, _)| {
558            if self.has_evidence(vidx) {
559                false
560            } else if let Some(known_endorsements) = self.endorsements.get(uhash) {
561                known_endorsements[vidx].is_none()
562            } else if let Some(known_endorsements) = self.incomplete_endorsements.get(uhash) {
563                !known_endorsements.contains_key(&vidx)
564            } else {
565                true
566            }
567        };
568        let new_endorsements = endorsements.endorsers.iter().filter(is_new_endorsement);
569
570        // For each new endorser, find a pair of conflicting endorsements, if it exists.
571        // Order the data so that the first unit has a lower or equal sequence number.
572        let conflicting_endorsements = new_endorsements.filter_map(|&(vidx, ref sig)| {
573            // Iterate over all existing endorsements by vidx.
574            let known_endorsements = self.endorsements.iter();
575            let known_incomplete_endorsements = self.incomplete_endorsements.iter();
576            let known_vidx_endorsements = known_endorsements
577                .filter_map(|(uhash2, sigs2)| sigs2[vidx].as_ref().map(|sig2| (uhash2, sig2)));
578            let known_vidx_incomplete_endorsements = known_incomplete_endorsements
579                .filter_map(|(uhash2, sigs2)| sigs2.get(&vidx).map(|sig2| (uhash2, sig2)));
580            // Find a conflicting one, i.e. one that endorses a unit with the same creator as uhash
581            // but incompatible with uhash. Put the data into a tuple, with the earlier unit first.
582            known_vidx_endorsements
583                .chain(known_vidx_incomplete_endorsements)
584                .find(|(uhash2, _)| {
585                    let unit2 = self.unit(uhash2);
586                    let ee_limit = self.params().endorsement_evidence_limit();
587                    self.unit(uhash2).creator == unit.creator
588                        && !self.is_compatible(uhash, uhash2)
589                        && unit.seq_number.saturating_add(ee_limit) >= unit2.seq_number
590                        && unit2.seq_number.saturating_add(ee_limit) >= unit.seq_number
591                })
592                .map(|(uhash2, sig2)| {
593                    if unit.seq_number <= self.unit(uhash2).seq_number {
594                        (vidx, uhash, sig, uhash2, sig2)
595                    } else {
596                        (vidx, uhash2, sig2, uhash, sig)
597                    }
598                })
599        });
600
601        // Create an Evidence instance for each conflict we found.
602        // The unwraps are safe, because we know that there are units with these hashes.
603        conflicting_endorsements
604            .map(|(vidx, uhash1, sig1, uhash2, sig2)| {
605                let unit1 = self.unit(uhash1);
606                let swimlane2 = self
607                    .swimlane(uhash2)
608                    .skip(1)
609                    .take_while(|(_, pred2)| pred2.seq_number >= unit1.seq_number)
610                    .map(|(pred2_hash, _)| self.wire_unit(pred2_hash, *instance_id).unwrap())
611                    .collect();
612                Evidence::Endorsements {
613                    endorsement1: SignedEndorsement::new(Endorsement::new(*uhash1, vidx), *sig1),
614                    unit1: self.wire_unit(uhash1, *instance_id).unwrap(),
615                    endorsement2: SignedEndorsement::new(Endorsement::new(*uhash2, vidx), *sig2),
616                    unit2: self.wire_unit(uhash2, *instance_id).unwrap(),
617                    swimlane2,
618                }
619            })
620            .collect()
621    }
622
623    /// Returns the `SignedWireUnit` with the given hash, if it is present in the state.
624    pub fn wire_unit(
625        &self,
626        hash: &C::Hash,
627        instance_id: C::InstanceId,
628    ) -> Option<SignedWireUnit<C>> {
629        let unit = self.maybe_unit(hash)?.clone();
630        let maybe_block = self.maybe_block(hash);
631        let value = maybe_block.map(|block| block.value.clone());
632        let endorsed = unit.claims_endorsed().cloned().collect();
633        #[allow(clippy::arithmetic_side_effects)] // min_round_length is guaranteed to be > 0.
634        let round_exp =
635            (unit.round_len() / self.params().min_round_length()).trailing_zeros() as u8;
636        let wunit = WireUnit {
637            panorama: unit.panorama.clone(),
638            creator: unit.creator,
639            instance_id,
640            value,
641            seq_number: unit.seq_number,
642            timestamp: unit.timestamp,
643            round_exp,
644            endorsed,
645        };
646        Some(SignedWireUnit {
647            hashed_wire_unit: HashedWireUnit::new_with_hash(wunit, *hash),
648            signature: unit.signature,
649        })
650    }
651
652    /// Returns the fork choice from `pan`'s view, or `None` if there are no blocks yet.
653    ///
654    /// The correct validators' latest units count as votes for the block they point to, as well as
655    /// all of its ancestors. At each level the block with the highest score is selected from the
656    /// children of the previously selected block (or from all blocks at height 0), until a block
657    /// is reached that has no children with any votes.
658    pub fn fork_choice<'a>(&'a self, pan: &Panorama<C>) -> Option<&'a C::Hash> {
659        let start = self.clock.start();
660        // Collect all correct votes in a `Tallies` map, sorted by height.
661        let to_entry = |(obs, w): (&Observation<C>, &Weight)| {
662            let bhash = &self.unit(obs.correct()?).block;
663            Some((self.block(bhash).height, bhash, *w))
664        };
665        let mut tallies: Tallies<C> = pan.iter().zip(&self.weights).filter_map(to_entry).collect();
666        loop {
667            // Find the highest block that we know is an ancestor of the fork choice.
668            let (height, bhash) = tallies.find_decided(self)?;
669            // Drop all votes that are not descendants of `bhash`.
670            tallies = tallies.filter_descendants(height, bhash, self);
671            // If there are no blocks left, `bhash` itself is the fork choice. Otherwise repeat.
672            if tallies.is_empty() {
673                let end = self.clock.end();
674                let delta = self.clock.delta(start, end).as_nanos();
675                trace!(%delta,"Time taken for fork-choice to run");
676                return Some(bhash);
677            }
678        }
679    }
680
681    /// Returns the ancestor of the block with the given `hash`, on the specified `height`, or
682    /// `None` if the block's height is lower than that.
683    /// NOTE: Panics if used on non-proposal hashes.
684    pub fn find_ancestor_proposal<'a>(
685        &'a self,
686        hash: &'a C::Hash,
687        height: u64,
688    ) -> Option<&'a C::Hash> {
689        let block = self.block(hash);
690        if block.height < height {
691            return None;
692        }
693        if block.height == height {
694            return Some(hash);
695        }
696        #[allow(clippy::arithmetic_side_effects)] // block.height > height, otherwise we returned.
697        let diff = block.height - height;
698        // We want to make the greatest step 2^i such that 2^i <= diff.
699        let max_i = log2(diff) as usize;
700        // A block at height > 0 always has at least its parent entry in skip_idx.
701        #[allow(clippy::arithmetic_side_effects)]
702        let i = max_i.min(block.skip_idx.len() - 1);
703        self.find_ancestor_proposal(&block.skip_idx[i], height)
704    }
705
706    /// Returns an error if `swunit` is invalid. This can be called even if the dependencies are
707    /// not present yet.
708    pub(crate) fn pre_validate_unit(&self, swunit: &SignedWireUnit<C>) -> Result<(), UnitError> {
709        let wunit = swunit.wire_unit();
710        let creator = wunit.creator;
711        if creator.0 as usize >= self.validator_count() {
712            error!("Nonexistent validator should be rejected in Highway::pre_validate_unit.");
713            return Err(UnitError::Creator); // Should be unreachable.
714        }
715        if Some(&Fault::Banned) == self.faults.get(&creator) {
716            return Err(UnitError::Banned);
717        }
718        let rl_millis = self.params.min_round_length().millis();
719        #[allow(clippy::arithmetic_side_effects)] // We check for overflow before the left shift.
720        if wunit.round_exp as u32 > rl_millis.leading_zeros()
721            || rl_millis << wunit.round_exp > self.params.max_round_length().millis()
722        {
723            return Err(UnitError::RoundLengthGreaterThanMaximum);
724        }
725        if wunit.value.is_none() && !wunit.panorama.has_correct() {
726            return Err(UnitError::MissingBlock);
727        }
728        if wunit.panorama.len() != self.validator_count() {
729            return Err(UnitError::PanoramaLength(wunit.panorama.len()));
730        }
731        if wunit.panorama[creator].is_faulty() {
732            return Err(UnitError::FaultyCreator);
733        }
734        Ok(())
735    }
736
737    /// Returns an error if `swunit` is invalid. Must only be called once `pre_validate_unit`
738    /// returned `Ok` and all dependencies have been added to the state.
739    pub(crate) fn validate_unit(&self, swunit: &SignedWireUnit<C>) -> Result<(), UnitError> {
740        let wunit = swunit.wire_unit();
741        let creator = wunit.creator;
742        let panorama = &wunit.panorama;
743        let timestamp = wunit.timestamp;
744        panorama.validate(self)?;
745        if panorama.iter_correct(self).any(|v| v.timestamp > timestamp)
746            || wunit.timestamp < self.params.start_timestamp()
747        {
748            return Err(UnitError::Timestamps);
749        }
750        if wunit.seq_number != panorama.next_seq_num(self, creator) {
751            return Err(UnitError::SequenceNumber);
752        }
753        #[allow(clippy::arithmetic_side_effects)] // We checked for overflow in pre_validate_unit.
754        let round_len =
755            TimeDiff::from_millis(self.params.min_round_length().millis() << wunit.round_exp);
756        let r_id = round_id(timestamp, round_len);
757        let maybe_prev_unit = wunit.previous().map(|vh| self.unit(vh));
758        if let Some(prev_unit) = maybe_prev_unit {
759            if prev_unit.round_len() != round_len {
760                // The round length must not change within a round: Even with respect to the
761                // greater of the two lengths, a round boundary must be between the units.
762                let max_rl = prev_unit.round_len().max(round_len);
763                #[allow(clippy::arithmetic_side_effects)] // max_rl is always greater than 0.
764                if prev_unit.timestamp.millis() / max_rl.millis()
765                    == timestamp.millis() / max_rl.millis()
766                {
767                    return Err(UnitError::RoundLengthChangedWithinRound);
768                }
769            }
770            // There can be at most two units per round: proposal/confirmation and witness.
771            if let Some(prev2_unit) = prev_unit.previous().map(|h2| self.unit(h2)) {
772                if prev2_unit.round_id() == r_id {
773                    return Err(UnitError::ThreeUnitsInRound);
774                }
775            }
776        }
777        if ENABLE_ENDORSEMENTS {
778            // All endorsed units from the panorama of this wunit.
779            let endorsements_in_panorama = panorama
780                .iter_correct_hashes()
781                .flat_map(|hash| self.unit(hash).claims_endorsed())
782                .collect::<HashSet<_>>();
783            if endorsements_in_panorama
784                .iter()
785                .any(|&e| !wunit.endorsed.iter().any(|h| h == e))
786            {
787                return Err(UnitError::EndorsementsNotMonotonic);
788            }
789            for hash in &wunit.endorsed {
790                if !wunit.panorama.sees(self, hash) {
791                    return Err(UnitError::EndorsedButUnseen {
792                        hash: format!("{:?}", hash),
793                        wire_unit: format!("{:?}", wunit),
794                    });
795                }
796            }
797        }
798        if wunit.value.is_some() {
799            // If this unit is a block, it must be the first unit in this round, its timestamp must
800            // match the round ID, and the creator must be the round leader.
801            if maybe_prev_unit.is_some_and(|pv| pv.round_id() == r_id)
802                || timestamp != r_id
803                || self.leader(r_id) != creator
804            {
805                return Err(UnitError::NonLeaderBlock(self.leader(r_id)));
806            }
807            // It's not allowed to create a child block of a terminal block.
808            let is_terminal = |hash: &C::Hash| self.is_terminal_block(hash);
809            if self.fork_choice(panorama).is_some_and(is_terminal) {
810                return Err(UnitError::ValueAfterTerminalBlock);
811            }
812        }
813        match self.validate_lnc(creator, panorama, &wunit.endorsed) {
814            None => Ok(()),
815            Some(vidx) => Err(UnitError::LncNaiveCitation(vidx)),
816        }
817    }
818
819    /// Returns `true` if the `bhash` is a block that can have no children.
820    pub(crate) fn is_terminal_block(&self, bhash: &C::Hash) -> bool {
821        self.blocks.get(bhash).is_some_and(|block| {
822            block.height.saturating_add(1) >= self.params.end_height()
823                && self.unit(bhash).timestamp >= self.params.end_timestamp()
824        })
825    }
826
827    /// Returns `true` if this is a proposal and the creator is not faulty.
828    pub(super) fn is_correct_proposal(&self, unit: &Unit<C>) -> bool {
829        !self.is_faulty(unit.creator)
830            && self.leader(unit.timestamp) == unit.creator
831            && unit.timestamp == round_id(unit.timestamp, unit.round_len)
832    }
833
834    /// Returns the hash of the message with the given sequence number from the creator of `hash`,
835    /// or `None` if the sequence number is higher than that of the unit with `hash`.
836    pub fn find_in_swimlane<'a>(
837        &'a self,
838        hash: &'a C::Hash,
839        seq_number: u64,
840    ) -> Option<&'a C::Hash> {
841        let unit = self.unit(hash);
842        match unit.seq_number.checked_sub(seq_number) {
843            None => None,          // There is no unit with seq_number in our swimlane.
844            Some(0) => Some(hash), // The sequence number is the one we're looking for.
845            Some(diff) => {
846                // We want to make the greatest step 2^i such that 2^i <= diff.
847                let max_i = log2(diff) as usize; // Log is safe because diff is not zero.
848
849                // Diff is not zero, so the unit has a predecessor and skip_idx is not empty.
850                #[allow(clippy::arithmetic_side_effects)]
851                let i = max_i.min(unit.skip_idx.len() - 1);
852                self.find_in_swimlane(&unit.skip_idx[i], seq_number)
853            }
854        }
855    }
856
857    /// Returns an iterator over units (with hashes) by the same creator, in reverse chronological
858    /// order, starting with the specified unit. Panics if no unit with `uhash` exists.
859    pub fn swimlane<'a>(
860        &'a self,
861        uhash: &'a C::Hash,
862    ) -> impl Iterator<Item = (&'a C::Hash, &'a Unit<C>)> {
863        let mut next = Some(uhash);
864        iter::from_fn(move || {
865            let current = next?;
866            let unit = self.unit(current);
867            next = unit.previous();
868            Some((current, unit))
869        })
870    }
871
872    /// Returns an iterator over all hashes of ancestors of the block `bhash`, excluding `bhash`
873    /// itself. Panics if `bhash` is not the hash of a known block.
874    pub fn ancestor_hashes<'a>(&'a self, bhash: &'a C::Hash) -> impl Iterator<Item = &'a C::Hash> {
875        let mut next = self.block(bhash).parent();
876        iter::from_fn(move || {
877            let current = next?;
878            next = self.block(current).parent();
879            Some(current)
880        })
881    }
882
883    /// Returns the number of units received.
884    #[cfg(test)]
885    pub(crate) fn unit_count(&self) -> usize {
886        self.units.len()
887    }
888
889    /// Returns the set of units (by hash) that are endorsed and seen from the panorama.
890    pub fn seen_endorsed(&self, pan: &Panorama<C>) -> BTreeSet<C::Hash> {
891        if !ENABLE_ENDORSEMENTS {
892            return Default::default();
893        };
894        // First we collect all units that were already seen as endorsed by earlier units.
895        let mut result: BTreeSet<C::Hash> = pan
896            .iter_correct_hashes()
897            .flat_map(|hash| self.unit(hash).endorsed.iter().cloned())
898            .collect();
899        // Now add all remaining endorsed units. Since the pan.sees check is expensive, do it only
900        // for the ones that are actually new.
901        for hash in self.endorsements.keys() {
902            if !result.contains(hash) && pan.sees(self, hash) {
903                result.insert(*hash);
904            }
905        }
906        result
907    }
908
909    /// Drops all state other than evidence.
910    pub(crate) fn retain_evidence_only(&mut self) {
911        self.units.clear();
912        self.blocks.clear();
913        for obs in self.panorama.iter_mut() {
914            if obs.is_correct() {
915                *obs = Observation::None;
916            }
917        }
918        self.endorsements.clear();
919        self.incomplete_endorsements.clear();
920    }
921
922    /// Validates whether a unit with the given panorama and `endorsed` set satisfies the
923    /// Limited Naivety Criterion (LNC).
924    /// Returns index of the first equivocator that was cited naively in violation of the LNC, or
925    /// `None` if the LNC is satisfied.
926    fn validate_lnc(
927        &self,
928        creator: ValidatorIndex,
929        panorama: &Panorama<C>,
930        endorsed: &BTreeSet<C::Hash>,
931    ) -> Option<ValidatorIndex> {
932        if !ENABLE_ENDORSEMENTS {
933            return None;
934        }
935        let violates_lnc =
936            |eq_idx: &ValidatorIndex| !self.satisfies_lnc_for(creator, panorama, endorsed, *eq_idx);
937        panorama.iter_faulty().find(violates_lnc)
938    }
939
940    /// Returns `true` if there is at most one fork by the validator `eq_idx` that is cited naively
941    /// by a unit with the given panorama and `endorsed` set, or earlier units by the same creator.
942    fn satisfies_lnc_for(
943        &self,
944        creator: ValidatorIndex,
945        panorama: &Panorama<C>,
946        endorsed: &BTreeSet<C::Hash>,
947        eq_idx: ValidatorIndex,
948    ) -> bool {
949        // Find all forks by eq_idx that are cited naively by the panorama itself.
950        // * If it's more than one, return false: the LNC is violated.
951        // * If it's none, return true: If the LNC were violated, it would be because of two naive
952        //   citations by creator's earlier units. So the latest of those earlier units would
953        //   already be violating the LNC itself, and thus would not have been added to the state.
954        // * Otherwise store the unique naively cited fork in naive_fork.
955        let mut maybe_naive_fork = None;
956        {
957            // Returns true if any endorsed unit cites the given unit.
958            let seen_by_endorsed = |hash| endorsed.iter().any(|e_hash| self.sees(e_hash, hash));
959
960            // Iterate over all units cited by the panorama.
961            let mut to_visit: Vec<_> = panorama.iter_correct_hashes().collect();
962            // This set is a filter so that units don't get added to to_visit twice.
963            let mut added_to_to_visit: HashSet<_> = to_visit.iter().cloned().collect();
964            while let Some(hash) = to_visit.pop() {
965                if seen_by_endorsed(hash) {
966                    continue; // This unit and everything below is not cited naively.
967                }
968                let unit = self.unit(hash);
969                match &unit.panorama[eq_idx] {
970                    Observation::Correct(eq_hash) => {
971                        // The unit (and everything it cites) can only see a single fork.
972                        // No need to traverse further downward.
973                        if !seen_by_endorsed(eq_hash) {
974                            // The fork is cited naively!
975                            match maybe_naive_fork {
976                                // It's the first naively cited fork we found.
977                                None => maybe_naive_fork = Some(eq_hash),
978                                Some(other_hash) => {
979                                    // If eq_hash is later than other_hash, it is the tip of the
980                                    // same fork. If it is earlier, then other_hash is the tip.
981                                    if self.sees_correct(eq_hash, other_hash) {
982                                        maybe_naive_fork = Some(eq_hash);
983                                    } else if !self.sees_correct(other_hash, eq_hash) {
984                                        return false; // We found two incompatible forks!
985                                    }
986                                }
987                            }
988                        }
989                    }
990                    // No forks are cited by this unit. No need to traverse further.
991                    Observation::None => (),
992                    // The unit still sees the equivocator as faulty: We need to traverse further
993                    // down the graph to find all cited forks.
994                    Observation::Faulty => to_visit.extend(
995                        unit.panorama
996                            .iter_correct_hashes()
997                            .filter(|hash| added_to_to_visit.insert(hash)),
998                    ),
999                }
1000            }
1001        }
1002        let naive_fork = match maybe_naive_fork {
1003            None => return true, // No forks are cited naively.
1004            Some(naive_fork) => naive_fork,
1005        };
1006
1007        // Iterate over all earlier units by creator, and find all forks by eq_idx they
1008        // naively cite. If any of those forks are incompatible with naive_fork, the LNC is
1009        // violated.
1010        let mut maybe_pred_hash = panorama[creator].correct();
1011        while let Some(pred_hash) = maybe_pred_hash {
1012            let pred_unit = self.unit(pred_hash);
1013            // Returns true if any endorsed (according to pred_unit) unit cites the given unit.
1014            let seen_by_endorsed = |hash| {
1015                pred_unit
1016                    .endorsed
1017                    .iter()
1018                    .any(|e_hash| self.sees(e_hash, hash))
1019            };
1020            // Iterate over all units seen by pred_unit.
1021            let mut to_visit = vec![pred_hash];
1022            // This set is a filter so that units don't get added to to_visit twice.
1023            let mut added_to_to_visit: HashSet<_> = to_visit.iter().cloned().collect();
1024            while let Some(hash) = to_visit.pop() {
1025                if seen_by_endorsed(hash) {
1026                    continue; // This unit and everything below is not cited naively.
1027                }
1028                let unit = self.unit(hash);
1029                match &unit.panorama[eq_idx] {
1030                    Observation::Correct(eq_hash) => {
1031                        if !seen_by_endorsed(eq_hash) && !self.is_compatible(eq_hash, naive_fork) {
1032                            return false;
1033                        }
1034                    }
1035                    // No forks are cited by this unit. No need to traverse further.
1036                    Observation::None => (),
1037                    // The unit still sees the equivocator as faulty: We need to traverse further
1038                    // down the graph to find all cited forks.
1039                    Observation::Faulty => to_visit.extend(
1040                        unit.panorama
1041                            .iter_correct_hashes()
1042                            .filter(|hash| added_to_to_visit.insert(hash)),
1043                    ),
1044                }
1045            }
1046            if !pred_unit.panorama[eq_idx].is_faulty() {
1047                // This unit and everything below sees only a single fork of the equivocator. If we
1048                // haven't found conflicting naively cited forks yet, there are none.
1049                return true;
1050            }
1051            maybe_pred_hash = pred_unit.previous();
1052        }
1053        true // No earlier messages, so no conflicting naively cited forks.
1054    }
1055
1056    /// Returns whether the unit with `hash0` sees the one with `hash1` (i.e. `hash0 ≥ hash1`),
1057    /// and sees `hash1`'s creator as correct.
1058    pub fn sees_correct(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool {
1059        hash0 == hash1 || self.unit(hash0).panorama.sees_correct(self, hash1)
1060    }
1061
1062    /// Returns whether the unit with `hash0` sees the one with `hash1` (i.e. `hash0 ≥ hash1`).
1063    pub fn sees(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool {
1064        hash0 == hash1 || self.unit(hash0).panorama.sees(self, hash1)
1065    }
1066
1067    // Returns whether the units with `hash0` and `hash1` see each other or are equal.
1068    fn is_compatible(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool {
1069        hash0 == hash1
1070            || self.unit(hash0).panorama.sees(self, hash1)
1071            || self.unit(hash1).panorama.sees(self, hash0)
1072    }
1073
1074    /// Returns the panorama of the confirmation for the leader unit `uhash`.
1075    pub fn confirmation_panorama(&self, creator: ValidatorIndex, uhash: &C::Hash) -> Panorama<C> {
1076        self.valid_panorama(creator, self.inclusive_panorama(uhash))
1077    }
1078
1079    /// Creates a panorama that is valid for use in `creator`'s next unit, and as close as possible
1080    /// to the given one. It is only modified if necessary for validity:
1081    /// * Cite `creator`'s previous unit, i.e. don't equivocate.
1082    /// * Satisfy the LNC, i.e. don't add new naively cited forks.
1083    pub fn valid_panorama(&self, creator: ValidatorIndex, mut pan: Panorama<C>) -> Panorama<C> {
1084        // Make sure the panorama sees the creator's own previous unit.
1085        let maybe_prev_uhash = self.panorama()[creator].correct();
1086        if let Some(prev_uhash) = maybe_prev_uhash {
1087            if pan[creator].correct() != Some(prev_uhash) {
1088                pan = pan.merge(self, &self.inclusive_panorama(prev_uhash));
1089            }
1090        }
1091        let endorsed = self.seen_endorsed(&pan);
1092        if self.validate_lnc(creator, &pan, &endorsed).is_none() {
1093            return pan;
1094        }
1095        // `pan` violates the LNC.
1096        // Start from the creator's previous unit, mark all faulty
1097        // validators as faulty, and add only endorsed units from correct validators.
1098        pan = maybe_prev_uhash.map_or_else(
1099            || Panorama::new(self.validator_count()),
1100            |prev_uhash| self.inclusive_panorama(prev_uhash),
1101        );
1102        for faulty_v in self.faulty_validators() {
1103            pan[faulty_v] = Observation::Faulty;
1104        }
1105        for endorsed_hash in &endorsed {
1106            if !pan.sees_correct(self, endorsed_hash) {
1107                pan = pan.merge(self, &self.inclusive_panorama(endorsed_hash));
1108            }
1109        }
1110        pan
1111    }
1112
1113    /// Returns panorama of a unit where latest entry of the creator is that unit's hash.
1114    pub fn inclusive_panorama(&self, uhash: &C::Hash) -> Panorama<C> {
1115        let unit = self.unit(uhash);
1116        let mut pan = unit.panorama.clone();
1117        pan[unit.creator] = Observation::Correct(*uhash);
1118        pan
1119    }
1120}
1121
1122/// Returns the time at which the round with the given timestamp and round length began.
1123///
1124/// The boundaries of rounds with length `l` are multiples of that length, in
1125/// milliseconds since the epoch. So the beginning of the current round is the greatest multiple
1126/// of `l` that is less or equal to `timestamp`.
1127pub(crate) fn round_id(timestamp: Timestamp, round_len: TimeDiff) -> Timestamp {
1128    if round_len.millis() == 0 {
1129        error!("called round_id with round_len 0.");
1130        return timestamp;
1131    }
1132    #[allow(clippy::arithmetic_side_effects)] // Checked for division by 0 above.
1133    Timestamp::from((timestamp.millis() / round_len.millis()) * round_len.millis())
1134}
1135
1136/// Returns the base-2 logarithm of `x`, rounded down, i.e. the greatest `i` such that
1137/// `2.pow(i) <= x`. If `x == 0`, it returns `0`.
1138fn log2(x: u64) -> u32 {
1139    // Find the least power of two strictly greater than x and count its trailing zeros.
1140    // Then subtract 1 to get the zeros of the greatest power of two less or equal than x.
1141    x.saturating_add(1)
1142        .checked_next_power_of_two()
1143        .unwrap_or(0)
1144        .trailing_zeros()
1145        .saturating_sub(1)
1146}