Skip to main content

dig_slashing/
constants.rs

1//! Protocol constants for dig-slashing.
2//!
3//! Traces to: [SPEC.md §2](../docs/resources/SPEC.md).
4//!
5//! Every value here is **protocol law** — changing any one constant requires a
6//! protocol version bump, not a bug fix. Downstream crates re-export from this
7//! module; no other file in this crate defines BPS values, quotients, or
8//! domain tags.
9//!
10//! # Why a flat module instead of a const enum
11//!
12//! The constants are consumed across almost every other module (evidence
13//! verification, appeal adjudication, reward math, inactivity accounting).
14//! A flat module with `pub const` items is the cheapest interface for that
15//! usage pattern — no trait dispatch, no import noise, and every reference
16//! shows up clearly in `cargo-udeps` / `gitnexus` impact analysis when a
17//! consumer is added or removed.
18
19// ── Penalty base rates (BPS — basis points, 10_000 = 100%) ──────────────────
20//
21// The BPS floor for each offense sets the minimum slash amount before
22// correlation amplification is applied at finalisation. The per-validator
23// slash is `max(eff_bal * base_bps / 10_000, eff_bal / MIN_SLASHING_PENALTY_QUOTIENT)`;
24// see SPEC §4 for the full formula and DSL-022 for the implementation.
25
26/// Base penalty for `OffenseType::ProposerEquivocation` — 5%.
27///
28/// Traces to SPEC §2.1. The heaviest floor of the four offenses: signing
29/// two distinct blocks at the same slot is the most blatant consensus
30/// misbehavior and indisputably intentional (an honest proposer produces
31/// exactly one block per slot).
32pub const EQUIVOCATION_BASE_BPS: u16 = 500;
33
34/// Base penalty for `OffenseType::InvalidBlock` — 3%.
35///
36/// Traces to SPEC §2.1. Less severe than equivocation because invalid
37/// blocks can result from bugs as well as malice, but still a protocol
38/// violation: the proposer signed a block that fails validation under the
39/// canonical rules.
40pub const INVALID_BLOCK_BASE_BPS: u16 = 300;
41
42/// Base penalty for both attester offenses (`AttesterDoubleVote`, `AttesterSurroundVote`) — 1%.
43///
44/// Traces to SPEC §2.1. Smaller per-validator floor because attestation
45/// offenses are mass-participation: a single `IndexedAttestation` can carry
46/// thousands of signers. Correlation amplification at finalisation
47/// (DSL-030 + DSL-151) scales the aggregate when many validators are caught.
48pub const ATTESTATION_BASE_BPS: u16 = 100;
49
50/// Maximum single-offense BPS floor — 10%.
51///
52/// Traces to SPEC §2.1. Invariant: every offense's `base_penalty_bps()`
53/// return value is `< MAX_PENALTY_BPS`. Correlation penalty at finalisation
54/// may exceed this cap (proportional slashing applies on top); this constant
55/// bounds only the initial optimistic debit.
56pub const MAX_PENALTY_BPS: u16 = 1_000;
57
58/// BPS denominator: `10_000` basis points = 100%.
59///
60/// Traces to SPEC §2.1. Divisor for every BPS-parameterised formula in
61/// the crate (base slash, correlation-penalty, bond-award split).
62/// Declared `u64` to match the numerator types in the slash formula
63/// and avoid per-call casts.
64pub const BPS_DENOMINATOR: u64 = 10_000;
65
66/// Ethereum-parity minimum-slashing-penalty quotient — `32`.
67///
68/// Traces to SPEC §2.2, §4. Sets the floor term `eff_bal /
69/// MIN_SLASHING_PENALTY_QUOTIENT` in the base slash formula (DSL-022).
70/// Guarantees a non-trivial burn even on low-bps offenses (100 bps
71/// attester votes → `eff_bal / 32` > `eff_bal / 100`).
72pub const MIN_SLASHING_PENALTY_QUOTIENT: u64 = 32;
73
74/// Minimum per-validator effective balance, in mojos — `32e9` (32 DIG).
75///
76/// Traces to SPEC §2.6. Anchors the bond-size constants
77/// (`REPORTER_BOND_MOJOS`, `APPELLANT_BOND_MOJOS`) and reward/penalty
78/// denominators. SPEC designates this as a re-export from
79/// `dig-consensus::MIN_VALIDATOR_COLLATERAL`; defined locally here
80/// while that crate is not yet on crates.io. Value must stay
81/// byte-identical to the upstream constant when the re-export lands.
82pub const MIN_EFFECTIVE_BALANCE: u64 = 32_000_000_000;
83
84/// Reporter bond required to submit slashing evidence — `MIN_EFFECTIVE_BALANCE / 64`.
85///
86/// Traces to SPEC §2.6, §12.3. Held in `BondEscrow` under
87/// `BondTag::Reporter(evidence_hash)` for the 8-epoch appeal window
88/// (DSL-023). Returned in full on finalisation (DSL-031) or forfeited
89/// on sustained appeal (DSL-068). Locked AFTER `verify_evidence` and
90/// BEFORE any `slash_absolute` call in `submit_evidence`.
91pub const REPORTER_BOND_MOJOS: u64 = MIN_EFFECTIVE_BALANCE / 64;
92
93/// Appellant bond required to file an appeal — same size as the
94/// reporter bond.
95///
96/// Traces to SPEC §2.6. Symmetric with `REPORTER_BOND_MOJOS` so the
97/// reporter and appellant face equal grief-vector costs.
98pub const APPELLANT_BOND_MOJOS: u64 = MIN_EFFECTIVE_BALANCE / 64;
99
100/// Exit-lock duration for a finalised slash — `100` epochs.
101///
102/// Traces to SPEC §2.2, §7.4 step 4. On finalisation (DSL-032) the
103/// manager calls `ValidatorEntry::schedule_exit(current_epoch +
104/// SLASH_LOCK_EPOCHS)` on every slashed validator — preventing
105/// voluntary exit + stake withdrawal before the correlation window
106/// tail-end + any follow-on slashes settle.
107pub const SLASH_LOCK_EPOCHS: u64 = 100;
108
109/// Appeal window length in epochs — `8`.
110///
111/// Traces to SPEC §2.6. A submitted `PendingSlash` can be appealed
112/// any time in `[submitted_at_epoch, submitted_at_epoch +
113/// SLASH_APPEAL_WINDOW_EPOCHS]`; after that the slash finalises
114/// (DSL-029). Ethereum parity: 2 epochs of ~6.4 min ≈ 12 min; DIG
115/// uses 8 epochs to match L2 block cadence.
116pub const SLASH_APPEAL_WINDOW_EPOCHS: u64 = 8;
117
118/// Ethereum Altair base-reward scaling factor.
119///
120/// Traces to SPEC §2.3, §8.3. Consumed by DSL-081
121/// `base_reward`. Larger factor → larger rewards at a given
122/// active-balance level; Ethereum mainnet value is 64.
123pub const BASE_REWARD_FACTOR: u64 = 64;
124
125/// Weight denominator for the Altair flag-reward split.
126///
127/// Traces to SPEC §2.3. Per-flag weights (`TIMELY_*_WEIGHT`)
128/// are specified in 64ths of the base reward. Unassigned
129/// weight (2 / 64 on mainnet — for the sync-committee slot we
130/// do not use) is an implicit zero-reward term.
131pub const WEIGHT_DENOMINATOR: u64 = 64;
132
133/// Reward weight for a correct SOURCE vote (TIMELY_SOURCE).
134///
135/// Traces to SPEC §2.3, §8.3. 14 / 64 ≈ 21.9% of base reward.
136pub const TIMELY_SOURCE_WEIGHT: u64 = 14;
137
138/// Reward weight for a correct TARGET vote (TIMELY_TARGET).
139///
140/// Traces to SPEC §2.3, §8.3. 26 / 64 ≈ 40.6% of base reward —
141/// highest of the three because target votes drive finality.
142pub const TIMELY_TARGET_WEIGHT: u64 = 26;
143
144/// Reward weight for a correct HEAD vote (TIMELY_HEAD).
145///
146/// Traces to SPEC §2.3, §8.3. 14 / 64 ≈ 21.9% — matches SOURCE.
147pub const TIMELY_HEAD_WEIGHT: u64 = 14;
148
149/// Epoch gap beyond which the network is in a finality stall.
150///
151/// Traces to SPEC §2.4, §9.1. `current_epoch - finalized_epoch >
152/// MIN_EPOCHS_TO_INACTIVITY_PENALTY` → inactivity-leak regime.
153/// Ethereum mainnet value = 4 epochs; below that the normal
154/// reward / penalty math runs unchanged.
155pub const MIN_EPOCHS_TO_INACTIVITY_PENALTY: u64 = 4;
156
157/// Per-epoch inactivity-score increment for a missed target
158/// vote during a finality stall.
159///
160/// Traces to SPEC §2.4, §9.2. Consumed by DSL-089. Ethereum
161/// mainnet value = 4. Outside a stall, misses do NOT add to
162/// the score (DSL-090 global recovery applies instead).
163pub const INACTIVITY_SCORE_BIAS: u64 = 4;
164
165/// Per-epoch global inactivity-score recovery applied once
166/// finality has resumed.
167///
168/// Traces to SPEC §2.4, §9.2. Consumed by DSL-090 — each
169/// validator's score shrinks by this amount (saturating at 0)
170/// every epoch outside a finality stall. Ethereum mainnet value
171/// = 16, so scores decay quickly once finality is restored.
172pub const INACTIVITY_SCORE_RECOVERY_RATE: u64 = 16;
173
174/// Divisor in the inactivity-penalty formula.
175///
176/// Traces to SPEC §2.4, §9.3. Consumed by DSL-092:
177/// `penalty = effective_balance * score /
178/// INACTIVITY_PENALTY_QUOTIENT` during a finality stall.
179/// Ethereum Bellatrix mainnet value = 2^24 (16_777_216).
180pub const INACTIVITY_PENALTY_QUOTIENT: u64 = 16_777_216;
181
182/// Proposer-inclusion reward weight (Ethereum Altair parity).
183///
184/// Traces to SPEC §2.3, §8.4. Consumed by DSL-085
185/// `proposer_inclusion_reward`. Proposer earns
186/// `attester_base * PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR -
187/// PROPOSER_WEIGHT)` = `base * 8 / 56` per attestation they are
188/// first to include.
189pub const PROPOSER_WEIGHT: u64 = 8;
190
191/// Minimum inclusion delay for an attestation to be
192/// reward-eligible, in slots.
193///
194/// Traces to SPEC §2.5. `inclusion_slot - data.slot` MUST be
195/// at least this value. `delay = 0` is impossible in the honest
196/// protocol (an attestation cannot be included in the block at
197/// its own slot) — the check is defensive.
198pub const MIN_ATTESTATION_INCLUSION_DELAY: u64 = 1;
199
200/// Maximum inclusion delay for an attestation to count as
201/// `TIMELY_SOURCE`, in slots.
202///
203/// Traces to SPEC §2.5, §8.1. Beyond this, the attestation is
204/// too stale to credit the source vote — the validator missed
205/// the justification window.
206pub const TIMELY_SOURCE_MAX_DELAY_SLOTS: u64 = 5;
207
208/// Maximum inclusion delay for an attestation to count as
209/// `TIMELY_TARGET`, in slots.
210///
211/// Traces to SPEC §2.5, §8.1. Equals `SLOTS_PER_EPOCH`: an
212/// attestation included within one epoch of its own slot can
213/// still contribute the target vote. Beyond that it is too
214/// stale.
215pub const TIMELY_TARGET_MAX_DELAY_SLOTS: u64 = 32;
216
217/// Bit index of the `TIMELY_SOURCE` flag in `ParticipationFlags`.
218///
219/// Traces to SPEC §2.9, §3.10. Ethereum Altair parity: source
220/// vote timely iff the attestation arrives within one epoch of
221/// the source-checkpoint boundary.
222pub const TIMELY_SOURCE_FLAG_INDEX: u8 = 0;
223
224/// Bit index of the `TIMELY_TARGET` flag in `ParticipationFlags`.
225///
226/// Traces to SPEC §2.9, §3.10.
227pub const TIMELY_TARGET_FLAG_INDEX: u8 = 1;
228
229/// Bit index of the `TIMELY_HEAD` flag in `ParticipationFlags`.
230///
231/// Traces to SPEC §2.9, §3.10. Head vote timely iff `inclusion_delay
232/// == 1` — only reachable when the attestation is included in the
233/// very next block.
234pub const TIMELY_HEAD_FLAG_INDEX: u8 = 2;
235
236/// 50/50 winner-award / burn split in basis points applied to a
237/// forfeited bond.
238///
239/// Traces to SPEC §2.6, §6.5. Consumed by DSL-068 (sustained →
240/// reporter bond forfeited, 50% routed to appellant, 50% burned)
241/// and DSL-071 (rejected → appellant bond forfeited, 50% to
242/// reporter, 50% burned). Expressed in basis points so future
243/// governance can tune it without changing the integer-division
244/// structure of the split.
245pub const BOND_AWARD_TO_WINNER_BPS: u64 = 5_000;
246
247/// Maximum serialized-bytes length of a `SlashAppeal` envelope.
248///
249/// Traces to SPEC §2.6, §6.1. Caps memory + DoS cost for
250/// invalid-block witness storage. Measured against the same
251/// bincode encoding used by `SlashAppeal::hash` (DSL-058) —
252/// deterministic, compact, length-prefixed. SPEC allows any
253/// canonical encoding; we pick bincode for parity with the
254/// `SlashingEvidence` envelope and to avoid pulling serde_json
255/// into the hot path.
256///
257/// Consumed by DSL-063 (`PayloadTooLarge`) rejection.
258pub const MAX_APPEAL_PAYLOAD_BYTES: usize = 131_072;
259
260/// Maximum distinct appeal attempts per pending slash.
261///
262/// Traces to SPEC §2.6, §6.1. Caps adjudication cost at a fixed
263/// upper bound per admitted evidence. Consumed by DSL-059
264/// (`TooManyAttempts`) rejection. Sustained attempts transition
265/// the slash to `Reverted` and the book entry is drained — they
266/// never contribute to this count.
267pub const MAX_APPEAL_ATTEMPTS_PER_SLASH: usize = 4;
268
269/// Maximum number of pending slashes the manager will track.
270///
271/// Traces to SPEC §2.6. Bounds memory + pruning cost. Admission at
272/// full capacity returns `SlashingError::PendingBookFull` (DSL-027).
273pub const MAX_PENDING_SLASHES: usize = 4_096;
274
275/// Block-level cap on evidence REMARKs — `64`.
276///
277/// Traces to SPEC §2.8 + §16.3. Evidence admitted in excess of
278/// this cap for a single block is rejected by
279/// [`crate::enforce_block_level_slashing_caps`] (DSL-108). Bounds
280/// per-block admission cost — each evidence triggers DSL-103
281/// puzzle-hash derivation + BLS verification downstream, so a
282/// hard cap keeps block-validation time predictable.
283pub const MAX_SLASH_PROPOSALS_PER_BLOCK: usize = 64;
284
285/// Block-level cap on appeal REMARKs — `64`.
286///
287/// Traces to SPEC §2.8 + §16.3. Symmetric to
288/// `MAX_SLASH_PROPOSALS_PER_BLOCK` but for appeal admission
289/// (DSL-119). Separate cap because appeal arrival is spiky
290/// (clustered at window expiry) and the two should not crowd
291/// each other out of a block's admission budget.
292pub const MAX_APPEALS_PER_BLOCK: usize = 64;
293
294/// Whistleblower reward divisor — `512`.
295///
296/// Traces to SPEC §2.3, §4. `wb_reward = total_eff_bal / 512`.
297/// Ethereum parity (equivalent role in consensus spec). Routed to the
298/// reporter's puzzle hash on admission (DSL-025), clawback-reversible
299/// on sustained appeal (DSL-067).
300pub const WHISTLEBLOWER_REWARD_QUOTIENT: u64 = 512;
301
302/// Ethereum-parity proportional-slashing multiplier — `3`.
303///
304/// Traces to SPEC §2.2, §4. Amplifies `cohort_sum` in the
305/// finalisation correlation penalty
306/// (`eff_bal * min(cohort_sum * 3, total_active) / total_active`),
307/// so coordinated-attack slashes are punished more than isolated ones.
308pub const PROPORTIONAL_SLASHING_MULTIPLIER: u64 = 3;
309
310/// Proposer inclusion-reward divisor — `8`.
311///
312/// Traces to SPEC §2.3. `prop_reward = wb_reward / 8`. Paid to the
313/// block proposer at the slot that includes the evidence — incentive
314/// for proposers to actually include the REMARK bundle carrying the
315/// evidence in the next block. Clawback-reversible on sustained appeal.
316pub const PROPOSER_REWARD_QUOTIENT: u64 = 8;
317
318// ── Domain separation tags (SPEC §2.10) ─────────────────────────────────────
319//
320// Byte-string tags prefixed into every SHA-256 digest so a hash produced for
321// one protocol context (e.g. an attester signing root) cannot be reinterpreted
322// in another context (e.g. a proposer signing root). These are `&[u8]`
323// constants because `chia_sha2::Sha256::update` accepts `impl AsRef<[u8]>`.
324//
325// Tags are frozen at protocol-version level; changes require a `_V2` rename
326// alongside the old tag kept live during migration.
327
328/// Domain tag for `AttestationData::signing_root` (DSL-004).
329///
330/// Traces to SPEC §2.10. Binds every attester BLS signing message to the
331/// attester-slashing / attestation-participation context so a signature
332/// produced here cannot be replayed as a proposer signature (DSL-050).
333pub const DOMAIN_BEACON_ATTESTER: &[u8] = b"DIG_BEACON_ATTESTER_V1";
334
335/// Domain tag for `SlashingEvidence::hash` (DSL-002).
336///
337/// Traces to SPEC §2.10, §3.5. Prefixed into the SHA-256 digest of a
338/// bincode-serialized `SlashingEvidence` envelope so the resulting hash
339/// cannot collide with any other protocol digest (attester signing root,
340/// appeal hash, REMARK wire hash). Used as the processed-map key
341/// (DSL-026) and the `BondTag::Reporter` binding (DSL-023); collision
342/// under either structure would cause double-slashing or bond misrouting.
343pub const DOMAIN_SLASHING_EVIDENCE: &[u8] = b"DIG_SLASHING_EVIDENCE_V1";
344
345/// Domain tag for `SlashAppeal::hash` (DSL-058, DSL-159).
346///
347/// Traces to SPEC §2.10, §3.7. Prefixed into the SHA-256 digest of
348/// a bincode-serialized `SlashAppeal` envelope so the appeal hash
349/// cannot collide with an evidence hash (`DOMAIN_SLASHING_EVIDENCE`)
350/// or any other protocol digest. Appeal hashes key
351/// `PendingSlash::appeal_history` entries (DSL-058 duplicate check)
352/// and are used by the adjudicator (DSL-070 `winning_appeal_hash`).
353pub const DOMAIN_SLASH_APPEAL: &[u8] = b"DIG_SLASH_APPEAL_V1";
354
355/// Domain tag for proposer `block_signing_message` (DSL-013, DSL-018).
356///
357/// Traces to SPEC §2.10, §5.2 step 6 + §5.4 step 1. Prefixed into the
358/// BLS signing message so a proposer signature produced for block
359/// production cannot be replayed as an attester signature (which uses
360/// `DOMAIN_BEACON_ATTESTER`) or any other context.
361pub const DOMAIN_BEACON_PROPOSER: &[u8] = b"DIG_BEACON_PROPOSER_V1";
362
363// ── REMARK wire magic prefixes (SPEC §4, §16.1, §16.2) ──────────────────────
364//
365// On-chain evidence + appeal payloads are carried inside CLVM `REMARK`
366// conditions whose byte contents begin with a namespace-and-version magic
367// prefix. The parser strips this prefix, serde_json-decodes the remainder,
368// and silently skips any payload whose prefix does not match — many
369// unrelated apps share the REMARK namespace, so skip-on-mismatch is the
370// ONLY correct policy (raising on every foreign payload would make the
371// parser unusable in production).
372//
373// The trailing NUL is an explicit terminator so the prefix can be
374// pattern-matched exactly without a length field. A future v2 format
375// coexists by using a distinct `_V2\0` suffix; we never reuse a magic.
376
377/// Magic prefix for evidence REMARK payloads (DSL-102).
378///
379/// Wire format: `SLASH_EVIDENCE_REMARK_MAGIC_V1 || serde_json(SlashingEvidence)`.
380/// Traces to SPEC §4 + §16.1.
381pub const SLASH_EVIDENCE_REMARK_MAGIC_V1: &[u8] = b"DIG_SLASH_EVIDENCE_V1\0";
382
383/// Magic prefix for appeal REMARK payloads (DSL-110).
384///
385/// Wire format: `SLASH_APPEAL_REMARK_MAGIC_V1 || serde_json(SlashAppeal)`.
386/// Traces to SPEC §4 + §16.2.
387pub const SLASH_APPEAL_REMARK_MAGIC_V1: &[u8] = b"DIG_SLASH_APPEAL_V1\0";
388
389// ── BLS widths (SPEC §2.10) ─────────────────────────────────────────────────
390//
391// Canonical BLS12-381 byte widths used by `chia-bls`. Re-declared here so
392// every wire-format check in this crate cites a single constant, and so
393// breaking upstream changes show up as compile-time edits to one place.
394
395/// BLS12-381 G2 signature compressed width (bytes).
396///
397/// Traces to SPEC §2.10. Used by `IndexedAttestation::validate_structure`
398/// (DSL-005) as the exact equality check on `signature.len()`.
399pub const BLS_SIGNATURE_SIZE: usize = 96;
400
401/// BLS12-381 G1 public key compressed width (bytes).
402///
403/// Traces to SPEC §2.10.
404pub const BLS_PUBLIC_KEY_SIZE: usize = 48;
405
406// ── Committee size cap (SPEC §2.7) ──────────────────────────────────────────
407
408/// Maximum number of validator indices in a single `IndexedAttestation`.
409///
410/// Traces to SPEC §2.7, Ethereum-parity value. Bounds memory + aggregate-
411/// verify cost per attestation. `IndexedAttestation::validate_structure`
412/// (DSL-005) rejects lengths strictly greater than this cap; the cap
413/// itself is valid (boundary behaviour enforced by
414/// `test_dsl_005_at_cap_accepted`).
415pub const MAX_VALIDATORS_PER_COMMITTEE: usize = 2_048;
416
417/// Maximum size of a slash-proposal payload (`failure_witness` bytes +
418/// appeal witness bytes combined) in bytes.
419///
420/// Traces to SPEC §2.7. Bounds memory + wire-size of evidence and
421/// appeals at 64 KiB — enough room for a block re-execution witness
422/// (trie proofs + state diff) without allowing unbounded adversary
423/// payloads. `verify_invalid_block` (DSL-018) rejects `failure_witness`
424/// with length `> MAX_SLASH_PROPOSAL_PAYLOAD_BYTES`; REMARK admission
425/// (DSL-109) mirrors this cap at the mempool layer.
426pub const MAX_SLASH_PROPOSAL_PAYLOAD_BYTES: usize = 65_536;