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}