Skip to main content

dig_slashing/participation/
flags.rs

1//! `ParticipationFlags` — 3-bit Ethereum-Altair-parity attestation
2//! flag bitmask.
3//!
4//! Traces to: [SPEC §3.10, §2.9](../../../docs/resources/SPEC.md),
5//! catalogue row
6//! [DSL-074](../../../docs/requirements/domains/participation/specs/DSL-074.md).
7//!
8//! # Bit layout
9//!
10//! | Bit | Flag                | Set iff |
11//! |-----|---------------------|---------|
12//! | 0   | `TIMELY_SOURCE`     | attestation's source matches the finalised checkpoint AND inclusion within `SLOTS_PER_EPOCH` of the target |
13//! | 1   | `TIMELY_TARGET`     | attestation's target matches the expected target root AND inclusion within `SLOTS_PER_EPOCH * SLOTS_PER_EPOCH` |
14//! | 2   | `TIMELY_HEAD`       | inclusion delay == 1 |
15//!
16//! Bits 3–7 are RESERVED — consumers MUST NOT assume they are
17//! zero across serialisation roundtrips (serde preserves the
18//! full `u8`).
19
20use serde::{Deserialize, Serialize};
21
22use crate::constants::{
23    TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX,
24};
25
26/// Per-validator attestation-participation bitmask.
27///
28/// Implements [DSL-074](../../../docs/requirements/domains/participation/specs/DSL-074.md).
29/// Traces to SPEC §3.10.
30///
31/// # Operations
32///
33/// - `set(flag_index)` — additive OR; idempotent.
34/// - `has(flag_index)` — read.
35/// - `is_source_timely` / `is_target_timely` / `is_head_timely`
36///   — named accessors mirroring Ethereum Altair nomenclature
37///   (spec §2.9).
38///
39/// # Design rationale
40///
41/// Using a single `u8` keeps the state tracker's per-validator
42/// storage at 1 byte — critical for the `ParticipationTracker`
43/// (DSL-078) which holds two epochs' worth of flags for every
44/// validator in the active set (millions of entries at scale).
45///
46/// # Default
47///
48/// `ParticipationFlags::default()` → all bits zero. Matches a
49/// validator that has not yet been credited with any flag in the
50/// current epoch.
51#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
52pub struct ParticipationFlags(pub u8);
53
54impl ParticipationFlags {
55    /// Additive OR-set of the bit at `flag_index`.
56    ///
57    /// Idempotent — calling `set(i)` twice leaves the bitmask
58    /// unchanged. Other bits are preserved.
59    ///
60    /// # Panics
61    ///
62    /// Does NOT panic for `flag_index >= 8` — the shift wraps in
63    /// release but evaluates to a no-op under valid protocol use
64    /// (only three flags are defined). Callers SHOULD stick to
65    /// the `TIMELY_*_FLAG_INDEX` constants.
66    pub fn set(&mut self, flag_index: u8) {
67        self.0 |= 1u8 << flag_index;
68    }
69
70    /// Read the bit at `flag_index`. `true` iff the flag was
71    /// ever `set()`.
72    #[must_use]
73    pub fn has(&self, flag_index: u8) -> bool {
74        (self.0 >> flag_index) & 1 == 1
75    }
76
77    /// Convenience: `has(TIMELY_SOURCE_FLAG_INDEX)`.
78    #[must_use]
79    pub fn is_source_timely(&self) -> bool {
80        self.has(TIMELY_SOURCE_FLAG_INDEX)
81    }
82
83    /// Convenience: `has(TIMELY_TARGET_FLAG_INDEX)`.
84    #[must_use]
85    pub fn is_target_timely(&self) -> bool {
86        self.has(TIMELY_TARGET_FLAG_INDEX)
87    }
88
89    /// Convenience: `has(TIMELY_HEAD_FLAG_INDEX)`.
90    #[must_use]
91    pub fn is_head_timely(&self) -> bool {
92        self.has(TIMELY_HEAD_FLAG_INDEX)
93    }
94}