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}