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