Skip to main content

dig_slashing/evidence/
attester_slashing.rs

1//! `AttesterSlashing` — pair of conflicting attestations plus the helper
2//! that extracts the slashable-validator set.
3//!
4//! Traces to: [SPEC.md §3.4](../../docs/resources/SPEC.md), catalogue row
5//! [DSL-007](../../docs/requirements/domains/evidence/specs/DSL-007.md).
6//!
7//! # Role
8//!
9//! Carries two `IndexedAttestation`s that are slashably conflicting under
10//! either the double-vote (DSL-014) or surround-vote (DSL-015) predicate.
11//! `slashable_indices()` returns the validator indices that signed BOTH —
12//! this is the per-validator fan-out consumed by the slash loop in
13//! `SlashingManager::submit_evidence` (DSL-022).
14//!
15//! # Preconditions for the helper
16//!
17//! Both `attesting_indices` MUST be strictly ascending and deduped —
18//! the contract `IndexedAttestation::validate_structure` (DSL-005)
19//! enforces. Callers invoke that guard FIRST; if structure is valid,
20//! `slashable_indices` is sound. If structure is malformed, the output
21//! is still deterministic but may not equal the set-theoretic intersection.
22//!
23//! # Why a two-pointer sweep
24//!
25//! `Vec<u32>` is already sorted ascending by precondition, so O(n+m)
26//! two-pointer walks beat `HashSet::intersection` (O(n+m) with hashing
27//! overhead + non-deterministic iteration order) and `retain` (O(n·m)).
28
29use std::cmp::Ordering;
30
31use serde::{Deserialize, Serialize};
32
33use crate::evidence::indexed_attestation::IndexedAttestation;
34
35/// A pair of conflicting indexed attestations.
36///
37/// Per [SPEC §3.4](../../docs/resources/SPEC.md). The two halves are
38/// `PartialEq`/`Eq` so higher-level code can detect the degenerate case
39/// where both attestations are byte-identical (appeal ground
40/// [DSL-041](../../../../docs/requirements/domains/appeal/specs/DSL-041.md)).
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct AttesterSlashing {
43    /// First attestation — arbitrary ordering; `slashable_indices` is
44    /// symmetric.
45    pub attestation_a: IndexedAttestation,
46    /// Second attestation.
47    pub attestation_b: IndexedAttestation,
48}
49
50impl AttesterSlashing {
51    /// Sorted, deduped intersection of the two attestations'
52    /// `attesting_indices`.
53    ///
54    /// Implements [DSL-007](../../docs/requirements/domains/evidence/specs/DSL-007.md).
55    /// Traces to SPEC §3.4.
56    ///
57    /// # Algorithm
58    ///
59    /// Two-pointer sweep. Assumes both index lists are already strictly
60    /// ascending (precondition established by
61    /// `IndexedAttestation::validate_structure`, DSL-005). For each
62    /// comparison:
63    ///
64    /// - **Equal** → emit to output, advance both pointers.
65    /// - **Less (`a[i] < b[j]`)** → advance `i`; `a[i]` cannot appear in `b`
66    ///   at or beyond `b[j]` (b is ascending, so smaller values cannot
67    ///   appear later).
68    /// - **Greater** → advance `j` for the mirror reason.
69    ///
70    /// O(n+m) comparisons, O(min(n,m)) output allocation.
71    ///
72    /// # Returns
73    ///
74    /// A strictly ascending `Vec<u32>` containing every validator index
75    /// that signed BOTH attestations. Disjoint inputs return `vec![]`.
76    ///
77    /// # Determinism
78    ///
79    /// Pure function of inputs. No hashing, no shared state, no iterator
80    /// with implementation-defined order. Repeated calls on the same
81    /// receiver return byte-equal results (enforced by
82    /// `test_dsl_007_deterministic`).
83    ///
84    /// # Downstream
85    ///
86    /// - `verify_attester_slashing` (DSL-016) rejects an empty result as
87    ///   `SlashingError::EmptySlashableIntersection`.
88    /// - `SlashingManager::submit_evidence` (DSL-022) iterates the result
89    ///   and invokes the base-slash debit per validator.
90    pub fn slashable_indices(&self) -> Vec<u32> {
91        let a = &self.attestation_a.attesting_indices;
92        let b = &self.attestation_b.attesting_indices;
93        let mut out: Vec<u32> = Vec::new();
94        let (mut i, mut j) = (0usize, 0usize);
95        while i < a.len() && j < b.len() {
96            match a[i].cmp(&b[j]) {
97                Ordering::Equal => {
98                    out.push(a[i]);
99                    i += 1;
100                    j += 1;
101                }
102                Ordering::Less => i += 1,
103                Ordering::Greater => j += 1,
104            }
105        }
106        out
107    }
108}