From 33303825cd164363bc662fe20641b0798631032c Mon Sep 17 00:00:00 2001 From: satyakwok <119509589+satyakwok@users.noreply.github.com> Date: Sun, 31 May 2026 23:54:33 +0200 Subject: [PATCH 1/2] feat(consensus): add STATE_IN_TRIE fork-gate scaffolding (SIP-6, PR 1/N) --- crates/sentrix-core/src/blockchain.rs | 4 +- crates/sentrix-core/src/fork_heights.rs | 52 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/crates/sentrix-core/src/blockchain.rs b/crates/sentrix-core/src/blockchain.rs index 660de41d..64cf6fba 100644 --- a/crates/sentrix-core/src/blockchain.rs +++ b/crates/sentrix-core/src/blockchain.rs @@ -36,8 +36,8 @@ pub use crate::fork_heights::{ get_add_self_stake_height, get_bft_gate_relax_height, get_evm_fork_height, get_evm_gas_fix_height, get_evm_value_transfer_height, get_extended_touch_list_height, get_jail_consensus_height, get_nft_tokenop_height, get_reward_v2_fork_height, - get_strict_justification_height, get_tokenomics_v2_height, get_voyager_fork_height, - warn_if_jail_consensus_armed, + get_state_in_trie_height, get_strict_justification_height, get_tokenomics_v2_height, + get_voyager_fork_height, warn_if_jail_consensus_armed, }; // Mempool consts live in `crate::mempool` now (next to the only code diff --git a/crates/sentrix-core/src/fork_heights.rs b/crates/sentrix-core/src/fork_heights.rs index c9218dc7..9fe40f21 100644 --- a/crates/sentrix-core/src/fork_heights.rs +++ b/crates/sentrix-core/src/fork_heights.rs @@ -139,6 +139,30 @@ const EXTENDED_TOUCH_LIST_HEIGHT_DEFAULT: u64 = u64::MAX; /// not the sender's `stake_weight` field. const STRICT_JUSTIFICATION_HEIGHT_DEFAULT: u64 = u64::MAX; +/// Bug A fix — off-trie consensus state migration to state trie +/// (SIP-6, drafted 2026-05-31 post testnet cascade 2026-05-30). +/// +/// Pre-fork: `pending_rewards`, `total_minted`, `liveness` +/// (`blocks_signed`/`blocks_missed`/`jail_*`), and `epoch_state` are +/// stored on `StakeRegistry`'s in-memory `HashMap` and slashing/epoch engines. Their mutations on the +/// apply path land AFTER state_root commitment, so silent drift across +/// validators is undetectable until a consuming tx (ClaimRewards, +/// Unjail, AddSelfStake) re-reads the diverged value and forks the +/// cluster. Surfaced on 2026-05-30 testnet: val3 ClaimRewards triggered +/// 3-way state fork at h=5,817,131 → cascade. +/// +/// Post-fork: those four consensus state pieces are written through +/// the state trie inside the apply path (before state_root is taken), +/// so any drift across validators surfaces immediately as a state_root +/// mismatch instead of silently accumulating. See +/// [SIP-6](https://github.com/sentrix-labs/SIPs/pull/3) for the +/// migration design (F2 — proper architectural fix; F3 +/// reconciliation and F1 disable-on-cascade were considered and +/// rejected). +const STATE_IN_TRIE_HEIGHT_DEFAULT: u64 = u64::MAX; +const STATE_IN_TRIE_HEIGHT_TESTNET_DEFAULT: u64 = u64::MAX; + // ── Runtime readers (env → u64, default to compile-time default) ────── fn configured_chain_id() -> u64 { @@ -347,6 +371,22 @@ pub fn get_strict_justification_height() -> u64 { ) } +/// State-in-trie fork height — SIP-6 Bug A migration. See +/// [`STATE_IN_TRIE_HEIGHT_DEFAULT`] for context + design link. +/// Default `u64::MAX` keeps the legacy off-trie write path +/// bit-identical; once an activation height is pinned via env, +/// the apply path routes the 4 consensus state pieces through the +/// state trie before state_root commitment. +pub fn get_state_in_trie_height() -> u64 { + read_height( + "STATE_IN_TRIE_HEIGHT", + chain_default( + STATE_IN_TRIE_HEIGHT_DEFAULT, + STATE_IN_TRIE_HEIGHT_TESTNET_DEFAULT, + ), + ) +} + // ── Height predicates ──────────────────────────────────── // // Every fork-height predicate has the same shape: @@ -465,6 +505,18 @@ pub fn is_strict_justification_height(height: u64) -> bool { fork != u64::MAX && height >= fork } +/// SIP-6 Bug A fix — true once `STATE_IN_TRIE_HEIGHT` activates. +/// Post-fork: the apply path commits `pending_rewards`, +/// `total_minted`, validator liveness, and `epoch_state` into the +/// state trie before state_root is taken — drift across validators +/// surfaces immediately as state_root mismatch instead of silent +/// off-trie divergence. Pre-fork the legacy in-memory write path is +/// preserved bit-identically. +pub fn is_state_in_trie_height(height: u64) -> bool { + let fork = get_state_in_trie_height(); + fork != u64::MAX && height >= fork +} + /// BFT-gate-relax: is the given height at or after the fork? /// Post-fork: validator-loop's P1 BFT safety gate uses /// `active >= ⌈2/3 × total⌉` instead of `active >= MIN_BFT_VALIDATORS From 720565732e59a8d6b49001e27b488067121ec708 Mon Sep 17 00:00:00 2001 From: satyakwok <119509589+satyakwok@users.noreply.github.com> Date: Sun, 31 May 2026 23:58:59 +0200 Subject: [PATCH 2/2] fix(sip-6): correct F1 label + drop unused testnet-default symmetry --- crates/sentrix-core/src/fork_heights.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/crates/sentrix-core/src/fork_heights.rs b/crates/sentrix-core/src/fork_heights.rs index 9fe40f21..0e6cfe19 100644 --- a/crates/sentrix-core/src/fork_heights.rs +++ b/crates/sentrix-core/src/fork_heights.rs @@ -157,11 +157,10 @@ const STRICT_JUSTIFICATION_HEIGHT_DEFAULT: u64 = u64::MAX; /// so any drift across validators surfaces immediately as a state_root /// mismatch instead of silently accumulating. See /// [SIP-6](https://github.com/sentrix-labs/SIPs/pull/3) for the -/// migration design (F2 — proper architectural fix; F3 -/// reconciliation and F1 disable-on-cascade were considered and -/// rejected). +/// migration design (F2 — proper architectural fix; F3 B3b-style +/// epoch-boundary reconciliation and F1 preemptive consuming-ops +/// gate were considered and rejected). const STATE_IN_TRIE_HEIGHT_DEFAULT: u64 = u64::MAX; -const STATE_IN_TRIE_HEIGHT_TESTNET_DEFAULT: u64 = u64::MAX; // ── Runtime readers (env → u64, default to compile-time default) ────── @@ -378,13 +377,7 @@ pub fn get_strict_justification_height() -> u64 { /// the apply path routes the 4 consensus state pieces through the /// state trie before state_root commitment. pub fn get_state_in_trie_height() -> u64 { - read_height( - "STATE_IN_TRIE_HEIGHT", - chain_default( - STATE_IN_TRIE_HEIGHT_DEFAULT, - STATE_IN_TRIE_HEIGHT_TESTNET_DEFAULT, - ), - ) + read_height("STATE_IN_TRIE_HEIGHT", STATE_IN_TRIE_HEIGHT_DEFAULT) } // ── Height predicates ────────────────────────────────────