Skip to main content

dig_epoch/
constants.rs

1//! # `constants` — compile-time epoch constants
2//!
3//! **Introduced by:** `STR-002` — Module hierarchy (SPEC §13).
4//!
5//! **Future owners:** This module is the landing zone for Phase 1 of
6//! `IMPLEMENTATION_ORDER.md`:
7//!
8//! - [`CON-001`](../../docs/requirements/domains/constants/specs/CON-001.md)
9//!   — epoch geometry (`BLOCKS_PER_EPOCH`, `EPOCH_L1_BLOCKS`, `GENESIS_HEIGHT`)
10//!   (landed)
11//! - [`CON-002`](../../docs/requirements/domains/constants/specs/CON-002.md)
12//!   — phase boundary thresholds (50 % / 75 % / 100 %)
13//! - [`CON-003`](../../docs/requirements/domains/constants/specs/CON-003.md)
14//!   — reward economics (`MOJOS_PER_L2`, `INITIAL_BLOCK_REWARD`, halvings,
15//!   tail emission, epoch-first-block bonus)
16//! - [`CON-004`](../../docs/requirements/domains/constants/specs/CON-004.md)
17//!   — 5-role fee / reward split percentages
18//! - [`CON-005`](../../docs/requirements/domains/constants/specs/CON-005.md)
19//!   — DFSP, consensus, slashing, and withdrawal constants
20//! - [`CON-006`](../../docs/requirements/domains/constants/specs/CON-006.md)
21//!   — sentinel constants (`EMPTY_ROOT`)
22//!
23//! **Spec reference:**
24//! [`SPEC.md` §13](../../docs/resources/SPEC.md) — canonical module list.
25//!
26//! ## Content rule
27//!
28//! Per the STR-002 responsibility table, this module holds **only**
29//! `pub const` declarations. It must never grow a function, enum, struct,
30//! or `use` import that has runtime semantics; cross-cutting concerns such
31//! as the genesis challenge come from `dig-constants` and are re-exported
32//! through `lib.rs`, not re-defined here.
33//!
34//! ## Status
35//!
36//! - **CON-001 (epoch geometry)** has landed — see the three `pub const`
37//!   items below.
38//! - CON-002 … CON-006 are still pending; their constants will be added
39//!   alongside this triple by their respective requirement commits.
40//!
41//! The [`STR_002_MODULE_PRESENT`] sentinel from STR-002 is kept in place;
42//! removing it is explicitly STR-002's cleanup task, not CON-001's, and
43//! pulling it out early would scope-creep across requirements.
44
45use chia_protocol::Bytes32;
46
47/// Sentinel marker proving the module exists, is declared in `lib.rs`, and
48/// is reachable from an external crate at its canonical path.
49///
50/// See the STR-002 integration test at
51/// [`tests/crate_structure/str_002_test.rs`](../../tests/crate_structure/str_002_test.rs).
52///
53/// The value is a zero-sized `()` constant; its only purpose is to let
54/// `use dig_epoch::constants::STR_002_MODULE_PRESENT` succeed. No production
55/// code should depend on it — later requirements are free to add real
56/// `pub const` items alongside it without coordination.
57#[doc(hidden)]
58pub const STR_002_MODULE_PRESENT: () = ();
59
60// -----------------------------------------------------------------------------
61// CON-001 — Epoch geometry
62// -----------------------------------------------------------------------------
63//
64// Spec refs:
65//   * Normative    : docs/requirements/domains/constants/NORMATIVE.md#con-001
66//   * Detailed spec: docs/requirements/domains/constants/specs/CON-001.md
67//   * SPEC source  : docs/resources/SPEC.md §2.1
68//   * Design basis : SPEC.md §1.3 Design Decisions #2 (epoch sizing) and #3
69//                    (height-1 as genesis)
70//
71// Values and types are **non-negotiable**. Changing any of them alters the
72// consensus-critical epoch geometry shared by every validator: all height-
73// to-epoch arithmetic (HEA-*), phase progression (PHS-*), reward emissions
74// (REW-*), and DFSP epoch boundaries (DFS-*) derive from this triple.
75//
76// Types in particular are load-bearing:
77//   * `u64` for `BLOCKS_PER_EPOCH` and `GENESIS_HEIGHT` — L2 heights are
78//     `u64`, so these constants slot into height arithmetic (e.g.
79//     `(height - GENESIS_HEIGHT) / BLOCKS_PER_EPOCH`) without casts.
80//   * `u32` for `EPOCH_L1_BLOCKS` — Chia L1 block heights are `u32`
81//     (chia-protocol / chia-consensus use 32-bit L1 heights), so phase
82//     progression computed as `(l1_now - l1_start) * 100 / EPOCH_L1_BLOCKS`
83//     stays inside `u32` without casts.
84//
85// Downstream consumers (non-exhaustive):
86//   * HEA-001 `epoch_for_block_height` — `(h - GENESIS_HEIGHT) / BLOCKS_PER_EPOCH`
87//   * HEA-002 `epoch_checkpoint_height` — `(e + 1) * BLOCKS_PER_EPOCH`
88//   * HEA-003 `is_genesis_checkpoint_block` — compares against `GENESIS_HEIGHT`
89//   * HEA-004 `l1_range_for_epoch` — multiplies by `EPOCH_L1_BLOCKS`
90//   * PHS-001 `l1_progress_phase_for_network_epoch` — uses `EPOCH_L1_BLOCKS`
91//     as the denominator of phase-progress percentage
92//   * REW-002 `total_block_reward` — uses `BLOCKS_PER_EPOCH` to identify the
93//     epoch-opening block that receives `EPOCH_FIRST_BLOCK_BONUS`
94//   * DFS-005 `is_dfsp_active_at_height` — heights are interpreted in the
95//     `BLOCKS_PER_EPOCH` geometry established here
96// -----------------------------------------------------------------------------
97
98/// L2 blocks per epoch — each epoch spans exactly this many committed heights.
99///
100/// **Value (locked by [SPEC §2.1](../../docs/resources/SPEC.md) and
101/// [CON-001](../../docs/requirements/domains/constants/specs/CON-001.md)):** `32`.
102///
103/// Epoch `e` contains heights `[e * BLOCKS_PER_EPOCH + 1, (e + 1) * BLOCKS_PER_EPOCH]`
104/// (half-open at the low end so that epoch 0 starts at [`GENESIS_HEIGHT`] and
105/// closed at the high end so that the epoch's last block is the checkpoint).
106///
107/// # Rationale
108///
109/// 32 blocks × 3 s wall-clock block time ≈ **96 seconds** per epoch — small
110/// enough for fast finality, large enough to amortise checkpoint overhead.
111/// See [SPEC.md §1.3 Design Decision #2](../../docs/resources/SPEC.md) for the
112/// full sizing argument. The value is also aligned with
113/// [`EPOCH_L1_BLOCKS`] so the two clocks (L2 chain and L1 phase window) tick
114/// at commensurate rates.
115///
116/// # Why `u64`
117///
118/// L2 heights are `u64` throughout dig-epoch (SPEC §2.1). Declaring
119/// `BLOCKS_PER_EPOCH` as `u64` lets callers write
120/// `(height - GENESIS_HEIGHT) / BLOCKS_PER_EPOCH` without an `as u64` cast,
121/// which both reads cleanly and avoids subtle truncation bugs. Any narrower
122/// type would force defensive casts at every use site; any wider type (e.g.
123/// `u128`) would unnecessarily poison arithmetic with a 128-bit upgrade.
124///
125/// # Consumers
126///
127/// This constant is read by the entire `height_epoch_arithmetic` domain
128/// (HEA-001 … HEA-005), the `reward_economics` domain (REW-002 / REW-005 use
129/// it to identify epoch boundaries for the first-block bonus and emission
130/// tally), the `epoch_manager` domain (MGR-004 `advance_epoch` counts blocks
131/// per epoch), and the `dfsp_processing` domain (DFS-005 activation height
132/// is rounded to epoch boundaries). Hard-coding `32` at a call site is a
133/// spec violation — callers MUST reference this symbol.
134pub const BLOCKS_PER_EPOCH: u64 = 32;
135
136/// L1 blocks per epoch window — the Chia L1 block count that maps to one
137/// dig-epoch phase cycle.
138///
139/// **Value (locked by [SPEC §2.1](../../docs/resources/SPEC.md) and
140/// [CON-001](../../docs/requirements/domains/constants/specs/CON-001.md)):** `32`.
141///
142/// At Chia's 18-second L1 block cadence this is ≈ **576 seconds (~10 minutes)**
143/// per window — the wall-clock envelope in which the four-phase epoch
144/// lifecycle (`BlockProduction` → `Checkpoint` → `Finalization` → `Complete`)
145/// runs. Phase progression is computed as `(l1_now - l1_start) * 100 /
146/// EPOCH_L1_BLOCKS`, which is then compared against the CON-002 boundary
147/// percentages (50 % / 75 % / 100 %).
148///
149/// # Rationale
150///
151/// 32 L1 blocks gives a comfortable window for block production, checkpoint
152/// collection, and finalization without pinning the phase clock to
153/// wall-clock time — which varies between nodes and drifts (SPEC §1.3
154/// Decision #1). Anchoring to L1 height makes phase progression objective
155/// and consensus-observable: every validator with the same L1 view computes
156/// the same phase.
157///
158/// # Why `u32`
159///
160/// Chia L1 heights are `u32` (chia-protocol / chia-consensus use 32-bit
161/// heights throughout the L1 ecosystem — see `Cargo.toml` for the version
162/// pins that lock this). Declaring `EPOCH_L1_BLOCKS` as `u32` keeps the
163/// phase-progress formula inside `u32` space without casts. A silent
164/// promotion to `u64` would force every caller to cast and would be
165/// incompatible with `chia-consensus` L1 height types.
166///
167/// # Consumers
168///
169/// Read by the `phase_state_machine` domain (PHS-001 … PHS-004) and by
170/// HEA-004 `l1_range_for_epoch`, which multiplies `EPOCH_L1_BLOCKS` by an
171/// epoch number to compute the L1 window for any epoch. Like
172/// [`BLOCKS_PER_EPOCH`], this symbol MUST be referenced by name — hard-coded
173/// `32` in phase or L1-range code is a spec violation.
174pub const EPOCH_L1_BLOCKS: u32 = 32;
175
176/// First L2 block height — the genesis block.
177///
178/// **Value (locked by [SPEC §2.1](../../docs/resources/SPEC.md) and
179/// [CON-001](../../docs/requirements/domains/constants/specs/CON-001.md)):** `1`.
180///
181/// The DIG L2 chain begins at height **1**, not height 0. The block at
182/// height 1 is the **epoch-0 genesis checkpoint** and is the sole occupant
183/// of the special genesis state in HEA-003's
184/// `is_genesis_checkpoint_block(h)` predicate.
185///
186/// # Rationale
187///
188/// Reserving `0` as "no blocks" removes an ambiguity that plagues
189/// zero-indexed chains: `height == 0` can mean either "the first block" or
190/// "the chain is empty", depending on the author. By starting at height 1
191/// (SPEC §1.3 Design Decision #3 and [`start.md` Hard Requirement 11](../../docs/prompt/start.md)),
192/// dig-epoch lets `u64` default-zero semantics serve as an explicit
193/// "uninitialised" marker (e.g. a freshly-constructed `EpochInfo` whose
194/// `current_height` is `0` is unambiguously pre-genesis).
195///
196/// This also lets the height-to-epoch formula stay simple:
197/// `epoch = (height - GENESIS_HEIGHT) / BLOCKS_PER_EPOCH`, which places both
198/// genesis (height 1) and the epoch-0 checkpoint (height 32) in epoch 0 by
199/// arithmetic — no special cases needed.
200///
201/// # Why `u64`
202///
203/// Matches the `u64` type used for every L2 block height in dig-epoch
204/// (SPEC §2.1). Using a matching type lets
205/// `(height - GENESIS_HEIGHT) / BLOCKS_PER_EPOCH` participate in native
206/// `u64` arithmetic.
207///
208/// # Consumers
209///
210/// HEA-001 / HEA-003 / HEA-005 (height arithmetic and the genesis predicate),
211/// MGR-001 / MGR-004 (the `EpochManager` initialises its head pointer at
212/// `GENESIS_HEIGHT - 1` so the first `record_block` call advances cleanly
213/// to genesis), and REW-002 (the epoch-opening bonus triggers when a
214/// recorded height equals `e * BLOCKS_PER_EPOCH + GENESIS_HEIGHT`).
215pub const GENESIS_HEIGHT: u64 = 1;
216
217// -----------------------------------------------------------------------------
218// CON-002 — Phase boundary percentages
219// -----------------------------------------------------------------------------
220//
221// Spec refs:
222//   * Normative    : docs/requirements/domains/constants/NORMATIVE.md#con-002
223//   * SPEC source  : docs/resources/SPEC.md §2.2
224//
225// These three values define where each active phase ends within the
226// EPOCH_L1_BLOCKS-wide L1 window. Phase progress is computed as:
227//   `progress_pct = (l1_now - epoch_start_l1) * 100 / EPOCH_L1_BLOCKS`
228// and compared against these thresholds in order:
229//   0 % … 50 %  → BlockProduction phase
230//   50 % … 75 % → Checkpoint phase
231//   75 % … 100 %→ Finalization phase
232//   >= 100 %    → Complete phase
233//
234// Types:
235//   * `u32` — L1 heights are `u32` (Chia), so the progress formula stays
236//     inside `u32` arithmetic without casts. The percentages are well within
237//     [0, 100], so no overflow risk.
238//
239// Values are **non-negotiable** (SPEC §2.2). Changing any of them shifts
240// phase windows for all validators and breaks consensus.
241// -----------------------------------------------------------------------------
242
243/// L1-progress percentage at which the BlockProduction phase ends.
244///
245/// **Value (locked by [SPEC §2.2]):** `50`.
246///
247/// When `(l1_now - epoch_start_l1) * 100 / EPOCH_L1_BLOCKS >= 50` the phase
248/// transitions from `BlockProduction` to `Checkpoint`.
249pub const PHASE_BLOCK_PRODUCTION_END_PCT: u32 = 50;
250
251/// L1-progress percentage at which the Checkpoint submission phase ends.
252///
253/// **Value (locked by [SPEC §2.2]):** `75`.
254///
255/// When progress reaches 75% the phase transitions from `Checkpoint` to
256/// `Finalization`.
257pub const PHASE_CHECKPOINT_END_PCT: u32 = 75;
258
259/// L1-progress percentage at which the Finalization phase ends.
260///
261/// **Value (locked by [SPEC §2.2]):** `100`.
262///
263/// When progress reaches 100% the phase transitions to `Complete`.
264pub const PHASE_FINALIZATION_END_PCT: u32 = 100;
265
266// -----------------------------------------------------------------------------
267// CON-003 — Reward economics constants
268// -----------------------------------------------------------------------------
269//
270// Spec refs:
271//   * Normative    : docs/requirements/domains/constants/NORMATIVE.md#con-003
272//   * Detailed spec: docs/requirements/domains/constants/specs/CON-003.md
273//   * SPEC source  : docs/resources/SPEC.md §2.3
274//
275// These are **economic commitments** identical across all validators. They
276// MUST NOT be runtime-configurable (SPEC §2.3).
277// -----------------------------------------------------------------------------
278
279/// 1 L2 token = 10^12 mojos.
280pub const MOJOS_PER_L2: u64 = 1_000_000_000_000;
281
282/// L2 block time in milliseconds.
283pub const L2_BLOCK_TIME_MS: u64 = 3_000;
284
285/// L2 blocks per 10-minute window (600_000 ms / 3_000 ms).
286pub const L2_BLOCKS_PER_10_MIN: u64 = 200;
287
288/// Initial emission rate: 64 L2 per 10 minutes.
289pub const INITIAL_EMISSION_PER_10_MIN: u64 = 64 * MOJOS_PER_L2;
290
291/// Tail emission rate: 4 L2 per 10 minutes.
292pub const TAIL_EMISSION_PER_10_MIN: u64 = 4 * MOJOS_PER_L2;
293
294/// Per-block reward before any halving (0.32 L2).
295/// Derived: `INITIAL_EMISSION_PER_10_MIN / L2_BLOCKS_PER_10_MIN`.
296pub const INITIAL_BLOCK_REWARD: u64 = 320_000_000_000;
297
298/// Per-block reward at tail emission (0.02 L2).
299/// Derived: `TAIL_EMISSION_PER_10_MIN / L2_BLOCKS_PER_10_MIN`.
300pub const TAIL_BLOCK_REWARD: u64 = 20_000_000_000;
301
302/// Halving interval: ~3 years of blocks at 3-second block time.
303pub const HALVING_INTERVAL_BLOCKS: u64 = 94_608_000;
304
305/// Number of halvings before switching to tail emission.
306pub const HALVINGS_BEFORE_TAIL: u64 = 4;
307
308/// Initial epoch reward (spec-declared; see CON-003.md).
309pub const INITIAL_EPOCH_REWARD: u64 = 32_000_000_000_000;
310
311/// Halving interval in epochs (spec-declared; see CON-003.md).
312pub const HALVING_INTERVAL_EPOCHS: u64 = 315_576;
313
314/// Minimum epoch reward (tail emission floor).
315pub const MINIMUM_EPOCH_REWARD: u64 = 2_000_000_000_000;
316
317/// Bonus reward for the first block after an epoch checkpoint.
318pub const EPOCH_FIRST_BLOCK_BONUS: u64 = 100_000_000_000;
319
320// -----------------------------------------------------------------------------
321// CON-004 — Fee and reward distribution constants
322// -----------------------------------------------------------------------------
323//
324// Spec refs:
325//   * Normative  : docs/requirements/domains/constants/NORMATIVE.md#con-004
326//   * SPEC source: docs/resources/SPEC.md §2.3
327//
328// Invariants (SPEC §2.3):
329//   FEE_PROPOSER_SHARE_PCT + FEE_BURN_SHARE_PCT == 100
330//   PROPOSER_REWARD_SHARE + ATTESTER_REWARD_SHARE + EF_SPAWNER_REWARD_SHARE
331//     + SCORE_SUBMITTER_REWARD_SHARE + FINALIZER_REWARD_SHARE == 100
332// -----------------------------------------------------------------------------
333
334/// Proposer share of collected transaction fees (percentage).
335pub const FEE_PROPOSER_SHARE_PCT: u64 = 50;
336
337/// Burn share of collected transaction fees (percentage).
338pub const FEE_BURN_SHARE_PCT: u64 = 50;
339
340/// Proposer share of the epoch block reward.
341pub const PROPOSER_REWARD_SHARE: u64 = 10;
342
343/// Attester share of the epoch block reward.
344pub const ATTESTER_REWARD_SHARE: u64 = 80;
345
346/// EF spawner share of the epoch block reward.
347pub const EF_SPAWNER_REWARD_SHARE: u64 = 3;
348
349/// Score submitter share of the epoch block reward.
350pub const SCORE_SUBMITTER_REWARD_SHARE: u64 = 4;
351
352/// Finalizer share of the epoch block reward.
353pub const FINALIZER_REWARD_SHARE: u64 = 3;
354
355// -----------------------------------------------------------------------------
356// CON-005 — DFSP, consensus, slashing, and withdrawal constants
357// -----------------------------------------------------------------------------
358//
359// Spec refs:
360//   * Normative    : docs/requirements/domains/constants/NORMATIVE.md#con-005
361//   * Detailed spec: docs/requirements/domains/constants/specs/CON-005.md
362//   * SPEC source  : docs/resources/SPEC.md §2.4-2.6
363// -----------------------------------------------------------------------------
364
365/// Wall-clock seconds per DFSP accounting epoch.
366/// Derived: `(BLOCKS_PER_EPOCH * L2_BLOCK_TIME_MS) / 1_000`.
367pub const DFSP_WALL_CLOCK_EPOCH_SECONDS: u64 = 96;
368
369/// Network epochs a CID may remain in Grace state before expiring (30-day window).
370pub const DFSP_GRACE_PERIOD_NETWORK_EPOCHS: u64 = 27_000;
371
372/// Bootstrap genesis issuance subsidy per evaluated epoch (mojos). `u128` for
373/// mojo-precision DFSP calculations.
374pub const DFSP_GENESIS_ISSUANCE_SUBSIDY_MOJOS_V1: u128 = 0;
375
376/// DFSP activation height — `u64::MAX` means disabled by default.
377pub const DFSP_ACTIVATION_HEIGHT: u64 = u64::MAX;
378
379/// Environment variable name used to override DFSP activation height.
380pub const DIG_DFSP_ACTIVATION_HEIGHT_ENV: &str = "DIG_DFSP_ACTIVATION_HEIGHT";
381
382/// Stake percentage required for soft finality.
383pub const SOFT_FINALITY_THRESHOLD_PCT: u64 = 67;
384
385/// Stake percentage required for a checkpoint to win the competition.
386pub const HARD_FINALITY_THRESHOLD_PCT: u64 = 67;
387
388/// Stake percentage required for a valid checkpoint submission.
389pub const CHECKPOINT_THRESHOLD_PCT: u64 = 67;
390
391/// Epochs to track for correlation penalty calculation. `u32` per SPEC §2.6.
392pub const CORRELATION_WINDOW_EPOCHS: u32 = 36;
393
394/// Maximum lookback for slashable offenses (in epochs).
395pub const SLASH_LOOKBACK_EPOCHS: u64 = 1_000;
396
397/// DFSP slashing evidence lookback — alias for [`SLASH_LOOKBACK_EPOCHS`].
398pub const DFSP_SLASH_LOOKBACK_EPOCHS: u64 = SLASH_LOOKBACK_EPOCHS;
399
400/// Epochs before a withdrawal completes.
401pub const WITHDRAWAL_DELAY_EPOCHS: u64 = 50;
402
403// -----------------------------------------------------------------------------
404// CON-006 — Sentinel constants
405// -----------------------------------------------------------------------------
406//
407// Spec refs:
408//   * Normative    : docs/requirements/domains/constants/NORMATIVE.md#con-006
409//   * Detailed spec: docs/requirements/domains/constants/specs/CON-006.md
410//   * SPEC source  : docs/resources/SPEC.md §7.1, 7.3, 14.1
411// -----------------------------------------------------------------------------
412
413/// SHA-256 hash of the empty string (`e3b0c44…b855`).
414///
415/// Used as the default root for empty Merkle trees (VER-001, VER-003) and
416/// as the initial value for all four DFSP roots in `EpochInfo::new()`.
417pub const EMPTY_ROOT: Bytes32 = Bytes32::new([
418    0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
419    0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
420]);