net/consensus: rolling checkpoints (Phase 1, self-rolling persistent) — activate at 1,055,555 #6

Closed
opened 2026-06-03 19:37:09 +00:00 by dobbscoin · 5 comments
dobbscoin commented 2026-06-03 19:37:09 +00:00

Goal

Persistent finality guard against deep reorgs from a malicious chain. Complements what's already shipped — MAX_REORG_DEPTH=100 (runtime, src/main.cpp:2498-2523) and the static h=976000 checkpoint (src/checkpoints.cpp::mapCheckpoints, commit 0c751a5) — by automatically advancing a runtime checkpoint as new blocks gain confirmation depth.

To be implemented after the monster begins to speak (post-fork, post-Codex-canon, post-OFFSIG-window).

Threat model addressed

  • Fresh-syncing node fed a malicious longer chain by a hostile peer
  • The >80%-hash attacker resurrecting an attack chain from before current tip
  • A node that's been offline rejoining and being targeted with a poisoned history

NOT addressed by this issue:

  • Near-tip reorg contention — covered by MAX_REORG_DEPTH=100
  • Pre-Restoration history — covered by the static h=976000 checkpoint
  • Centralized override / emergency consensus — see "Phase 2 (deferred)" below

Constants

Constant Value Rationale
HARDFORK_ROLLING_CKPT_MAIN_OFF 1,055,555 One past the end of the OFFSIG window (1,050,666). The Conclave-only mining period finishes first; auto-rolling never engages while OFFSIG blocks are still landing. Repeating-5s for chain-numerology consistency.
HARDFORK_ROLLING_CKPT_TESTNET_OFF 100 Trivial; mirrors how LWMA-3 was tested on testnet.
ROLLING_DEPTH 1023 2^10 - 1 (binary mysticism), carries the 23 motif from 23skidoo.info. Depth ≈ 17h at 60s — 10× MAX_REORG_DEPTH, comfortably past plausible legitimate-reorg territory.
ROLLING_KEEP 10000 GC ceiling for in-memory rolling entries. ~7 days at 60s. ~360 KB on disk.

Phase 1 — self-rolling persistent (THIS ISSUE)

Behavior

On every accepted block where pindex->nHeight >= HARDFORK_ROLLING_CKPT_MAIN_OFF + ROLLING_DEPTH:

  1. Look up the ancestor at pindex->nHeight - ROLLING_DEPTH.
  2. Insert (ancestor_height, ancestor_hash) into the runtime mapCheckpoints.
  3. Append the record to <datadir>/rolling_checkpoints.dat.
  4. If the runtime map exceeds ROLLING_KEEP, drop the oldest rolling entry (static entries are never dropped).

On startup:

  1. Load static mapCheckpoints as today.
  2. Then load rolling_checkpoints.dat (if present) and merge entries with height > max(static) into mapCheckpoints.
  3. Validation continues to flow through the existing Checkpoints::CheckBlock path — no parallel code path.

Files touched

  • src/checkpoints.{h,cpp} — make mapCheckpoints mutable at runtime; add LoadRollingCheckpoints(), WriteRollingCheckpoint(height, hash), MaybeRollForward(const CBlockIndex* pindexNew), GCRollingCheckpoints().
  • src/main.cpp — call Checkpoints::MaybeRollForward(pindexNew) at the tail of ConnectTip after chainActive.SetTip.
  • src/init.cpp — Checkpoints::LoadRollingCheckpoints() at startup, guarded by -rollingcheckpoints flag (default 1).
  • src/pow.h — HARDFORK_ROLLING_CKPT_MAIN_OFF / HARDFORK_ROLLING_CKPT_TESTNET_OFF constants.
  • src/rpcmisc.cpp (or wherever new RPCs land in OFF's pre-0.12 tree) — new RPCs:
    • getrollingcheckpoints — list current rolling entries
    • clearrollingcheckpoints <below_height> — ops recovery if a node ever locks bad state
    • setrollingcheckpointsenabled <true|false> — runtime toggle without restart

On-disk format (rolling_checkpoints.dat)

Append-only, fixed-width records:

struct rolling_record {
    uint32_t height;     // little-endian
    uint256  block_hash; // 32 bytes
};                       // 36 bytes per record

Loader is tolerant of partial-write tail (truncate the final partial record on load). No header, no version byte — if we ever need a v2 format, we just write to a new filename.

Defaults / escape hatches

  • Default on at activation (-rollingcheckpoints=1).
  • -rollingcheckpoints=0 startup flag fully disables the feature (skip load, skip write, skip MaybeRollForward).
  • clearrollingcheckpoints <below_height> RPC for ops recovery if a node locks bad state somehow.
  • Static mapCheckpoints entries are never overwritten or dropped by rolling code.

Phase 2 — Conclave-signed override (DEFERRED)

Out of scope for this issue. Tracked separately. Outline for reference:

  • New P2P message signedcheckpoint carrying (height, hash, key_index, ECDSA_sig), signed by any 1 of the 7 Conclave keys.
  • When received and valid, supersedes a conflicting auto-rolled entry.
  • Persisted to signed_checkpoints.dat.
  • Mirrors Bitcoin's old checkpoint P2P message (removed upstream by Wuille for centralization reasons — but we already trust the Conclave for OFFSIG + Treasury, so the trust model is internally consistent).
  • Worth its own design discussion: M-of-N vs 1-of-N authorization, gossip rules, behavior when a signed checkpoint conflicts with an auto-rolled one a node has already locked in, etc.

Open a follow-up issue when Phase 1 has settled in.

Risks / mitigations

Risk Mitigation
Bricked node from fluke-fork rollup Depth=1023 means 1023 confirms required before lock. -rollingcheckpoints=0 + clearrollingcheckpoints RPC as escape hatches.
Disk growth 10k cap × 36 bytes = ~360 KB ceiling. Trivial.
Interaction with Codex inscription / OFFSIG window Activation height 1,055,555 is post-OFFSIG (window ends at 1,050,666). Rolling never engages while Conclave-only mining is active.
Explorer txindex secondary daemon Picks this up for free — same source. No separate work needed.
Existing static h=976000 checkpoint Untouched. Rolling code only appends entries with height > max(static).

Activation milestone (to add to WHERE_WE_LEFT_OFF.md)

rolling checkpoints: 1,055,555

References

  • src/main.cpp:2498-2523 — existing MAX_REORG_DEPTH=100 runtime guard
  • src/checkpoints.cpp::mapCheckpoints — static checkpoint at h=976000 (commit 0c751a5)
  • src/pow.h — HARDFORK_LWMA3_MAIN_OFF=980000, nRestorationForkHeight=1000000
  • src/chainparams.cpp:167-168 — OFFSIG window 999,991-1,050,666
## Goal Persistent finality guard against deep reorgs from a malicious chain. Complements what's already shipped — `MAX_REORG_DEPTH=100` (runtime, `src/main.cpp:2498-2523`) and the static h=976000 checkpoint (`src/checkpoints.cpp::mapCheckpoints`, commit `0c751a5`) — by automatically advancing a runtime checkpoint as new blocks gain confirmation depth. To be implemented **after the monster begins to speak** (post-fork, post-Codex-canon, post-OFFSIG-window). ## Threat model addressed - Fresh-syncing node fed a malicious longer chain by a hostile peer - The >80%-hash attacker resurrecting an attack chain from before current tip - A node that's been offline rejoining and being targeted with a poisoned history NOT addressed by this issue: - Near-tip reorg contention — covered by `MAX_REORG_DEPTH=100` - Pre-Restoration history — covered by the static h=976000 checkpoint - Centralized override / emergency consensus — see "Phase 2 (deferred)" below ## Constants | Constant | Value | Rationale | |---|---|---| | `HARDFORK_ROLLING_CKPT_MAIN_OFF` | **1,055,555** | One past the end of the OFFSIG window (1,050,666). The Conclave-only mining period finishes first; auto-rolling never engages while OFFSIG blocks are still landing. Repeating-5s for chain-numerology consistency. | | `HARDFORK_ROLLING_CKPT_TESTNET_OFF` | 100 | Trivial; mirrors how LWMA-3 was tested on testnet. | | `ROLLING_DEPTH` | **1023** | `2^10 - 1` (binary mysticism), carries the **23** motif from `23skidoo.info`. Depth ≈ 17h at 60s — 10× `MAX_REORG_DEPTH`, comfortably past plausible legitimate-reorg territory. | | `ROLLING_KEEP` | 10000 | GC ceiling for in-memory rolling entries. ~7 days at 60s. ~360 KB on disk. | ## Phase 1 — self-rolling persistent (THIS ISSUE) ### Behavior On every accepted block where `pindex->nHeight >= HARDFORK_ROLLING_CKPT_MAIN_OFF + ROLLING_DEPTH`: 1. Look up the ancestor at `pindex->nHeight - ROLLING_DEPTH`. 2. Insert `(ancestor_height, ancestor_hash)` into the runtime `mapCheckpoints`. 3. Append the record to `<datadir>/rolling_checkpoints.dat`. 4. If the runtime map exceeds `ROLLING_KEEP`, drop the oldest rolling entry (static entries are never dropped). On startup: 1. Load static `mapCheckpoints` as today. 2. Then load `rolling_checkpoints.dat` (if present) and merge entries with `height > max(static)` into `mapCheckpoints`. 3. Validation continues to flow through the existing `Checkpoints::CheckBlock` path — no parallel code path. ### Files touched - `src/checkpoints.{h,cpp}` — make `mapCheckpoints` mutable at runtime; add `LoadRollingCheckpoints()`, `WriteRollingCheckpoint(height, hash)`, `MaybeRollForward(const CBlockIndex* pindexNew)`, `GCRollingCheckpoints()`. - `src/main.cpp` — call `Checkpoints::MaybeRollForward(pindexNew)` at the tail of `ConnectTip` after `chainActive.SetTip`. - `src/init.cpp` — `Checkpoints::LoadRollingCheckpoints()` at startup, guarded by `-rollingcheckpoints` flag (default **`1`**). - `src/pow.h` — `HARDFORK_ROLLING_CKPT_MAIN_OFF` / `HARDFORK_ROLLING_CKPT_TESTNET_OFF` constants. - `src/rpcmisc.cpp` (or wherever new RPCs land in OFF's pre-0.12 tree) — new RPCs: - `getrollingcheckpoints` — list current rolling entries - `clearrollingcheckpoints <below_height>` — ops recovery if a node ever locks bad state - `setrollingcheckpointsenabled <true|false>` — runtime toggle without restart ### On-disk format (`rolling_checkpoints.dat`) Append-only, fixed-width records: ``` struct rolling_record { uint32_t height; // little-endian uint256 block_hash; // 32 bytes }; // 36 bytes per record ``` Loader is tolerant of partial-write tail (truncate the final partial record on load). No header, no version byte — if we ever need a v2 format, we just write to a new filename. ### Defaults / escape hatches - **Default on** at activation (`-rollingcheckpoints=1`). - `-rollingcheckpoints=0` startup flag fully disables the feature (skip load, skip write, skip MaybeRollForward). - `clearrollingcheckpoints <below_height>` RPC for ops recovery if a node locks bad state somehow. - Static `mapCheckpoints` entries are never overwritten or dropped by rolling code. ## Phase 2 — Conclave-signed override (DEFERRED) Out of scope for this issue. Tracked separately. Outline for reference: - New P2P message `signedcheckpoint` carrying `(height, hash, key_index, ECDSA_sig)`, signed by any 1 of the 7 Conclave keys. - When received and valid, supersedes a conflicting auto-rolled entry. - Persisted to `signed_checkpoints.dat`. - Mirrors Bitcoin's old `checkpoint` P2P message (removed upstream by Wuille for centralization reasons — but we already trust the Conclave for OFFSIG + Treasury, so the trust model is internally consistent). - Worth its own design discussion: M-of-N vs 1-of-N authorization, gossip rules, behavior when a signed checkpoint conflicts with an auto-rolled one a node has already locked in, etc. Open a follow-up issue when Phase 1 has settled in. ## Risks / mitigations | Risk | Mitigation | |---|---| | Bricked node from fluke-fork rollup | Depth=1023 means 1023 confirms required before lock. `-rollingcheckpoints=0` + `clearrollingcheckpoints` RPC as escape hatches. | | Disk growth | 10k cap × 36 bytes = ~360 KB ceiling. Trivial. | | Interaction with Codex inscription / OFFSIG window | Activation height 1,055,555 is post-OFFSIG (window ends at 1,050,666). Rolling never engages while Conclave-only mining is active. | | Explorer txindex secondary daemon | Picks this up for free — same source. No separate work needed. | | Existing static h=976000 checkpoint | Untouched. Rolling code only appends entries with `height > max(static)`. | ## Activation milestone (to add to WHERE_WE_LEFT_OFF.md) `rolling checkpoints: 1,055,555` ## References - `src/main.cpp:2498-2523` — existing `MAX_REORG_DEPTH=100` runtime guard - `src/checkpoints.cpp::mapCheckpoints` — static checkpoint at h=976000 (commit `0c751a5`) - `src/pow.h` — `HARDFORK_LWMA3_MAIN_OFF=980000`, `nRestorationForkHeight=1000000` - `src/chainparams.cpp:167-168` — OFFSIG window 999,991-1,050,666
dobbscoin commented 2026-06-15 14:17:35 +00:00

Survey closed — merge-mining is not a viable alternative for the same threat surface

For the record before Phase 1 lands: the "could we just AuxPoW against Quarkcoin (QRK) instead of building rolling checkpoints?" question got a look. The conclusion is no, and the reason is worth keeping on file so the next contributor who asks finds it instead of redoing the survey.

Visible Quark auto-switch market

Source Quark hashrate
minerstat — aggregate across the 4 multipools they track (Mining Dutch, NiceHash, Zergpool, zpool) 22.56 MH/s
zpool.ca/api/status — quark algo entry absent (algo not currently offered)
Zergpool public API 403, gated
Mining Dutch TLS hostname busted, site appears abandoned
Kryptex /qrk 404

Aggregated visible Quark hashrate is roughly one decent GPU. There is no auto-switch ecosystem to recruit, and there is no organized QRK community to coordinate with — every social-channel search yields QuarkChain (QKC, Ethash) noise instead of QRK signal.

The asymmetry

QRK's chain itself is alive and ticking — chainz.cryptoid.info/qrk/ showed 41 new blocks in roughly 5 minutes of polling, and getdifficulty returned 7995.84. Through the same H = D × 2³² / T calibration the project already uses (validated against OFF current diff=13.02 / T=60s ⇒ ~932 MH/s, and against (BOB) diff=3.52 / T=120s ⇒ ~126 MH/s matching their published ~135 MH/s):

QRK ≈ 7995.84 × 2^32 / 30s ≈ 1.14 TH/s

That is ~50,000× the visible multipool number. The hashrate exists; it is not flowing through any open pool. The most plausible explanation is one or two legacy Quark/X11 ASICs (Baikal Quad-Mini class, ~7 TH/s/unit) pointed at QRK by a single unidentified operator.

Why this makes merge-mining worse than no parent

AuxPoW inherits the parent's security model. Merge-mining a parent whose hashrate is ~99.99% concentrated in one anonymous operator doesn't import security — it imports a single point of failure wearing a parent's costume. That operator can lose interest, have hardware die, or be coerced, and OFF would discover the dependency the moment they did. Compared to OFF's current standalone Quark posture, that is strictly worse, not better.

Implication for this issue

The threat surface AuxPoW theoretically addresses (51% attacks via rented or borrowed hashrate against a low-hashrate child chain) is already covered by OFFSIG during the signed-mining window upstream of activation, and by Phase 1 from h=1,055,555 onward. Rolling checkpoints don't require finding an honest external hashrate donor — because at Quark scale in 2026, that donor does not exist. The Quark-algorithm graveyard is not a security partner.

Closing the merge-mining branch. Phase 1 stands as the right path.

Sources (for future re-verification)

## Survey closed — merge-mining is not a viable alternative for the same threat surface For the record before Phase 1 lands: the "could we just AuxPoW against Quarkcoin (QRK) instead of building rolling checkpoints?" question got a look. The conclusion is **no**, and the reason is worth keeping on file so the next contributor who asks finds it instead of redoing the survey. ### Visible Quark auto-switch market | Source | Quark hashrate | |---|---| | `minerstat` — aggregate across the 4 multipools they track (Mining Dutch, NiceHash, Zergpool, zpool) | **22.56 MH/s** | | `zpool.ca/api/status` — `quark` algo entry | **absent** (algo not currently offered) | | Zergpool public API | 403, gated | | Mining Dutch | TLS hostname busted, site appears abandoned | | Kryptex `/qrk` | 404 | Aggregated *visible* Quark hashrate is roughly one decent GPU. There is no auto-switch ecosystem to recruit, and there is no organized QRK community to coordinate with — every social-channel search yields QuarkChain (QKC, Ethash) noise instead of QRK signal. ### The asymmetry QRK's chain itself is alive and ticking — `chainz.cryptoid.info/qrk/` showed 41 new blocks in roughly 5 minutes of polling, and `getdifficulty` returned `7995.84`. Through the same `H = D × 2³² / T` calibration the project already uses (validated against OFF current `diff=13.02 / T=60s ⇒ ~932 MH/s`, and against (BOB) `diff=3.52 / T=120s ⇒ ~126 MH/s` matching their published ~135 MH/s): ``` QRK ≈ 7995.84 × 2^32 / 30s ≈ 1.14 TH/s ``` That is **~50,000× the visible multipool number**. The hashrate exists; it is not flowing through any open pool. The most plausible explanation is one or two legacy Quark/X11 ASICs (Baikal Quad-Mini class, ~7 TH/s/unit) pointed at QRK by a single unidentified operator. ### Why this makes merge-mining worse than no parent AuxPoW inherits the parent's security model. Merge-mining a parent whose hashrate is ~99.99% concentrated in one anonymous operator doesn't import security — it imports a **single point of failure wearing a parent's costume**. That operator can lose interest, have hardware die, or be coerced, and OFF would discover the dependency the moment they did. Compared to OFF's current standalone Quark posture, that is strictly worse, not better. ### Implication for this issue The threat surface AuxPoW theoretically addresses (51% attacks via rented or borrowed hashrate against a low-hashrate child chain) is already covered by OFFSIG during the signed-mining window upstream of activation, and by Phase 1 from `h=1,055,555` onward. Rolling checkpoints don't require finding an honest external hashrate donor — because at Quark scale in 2026, that donor does not exist. The Quark-algorithm graveyard is not a security partner. Closing the merge-mining branch. Phase 1 stands as the right path. ### Sources (for future re-verification) - [minerstat — Quark mining pools (aggregate 22.56 MH/s)](https://minerstat.com/algorithm/quark/pools) - [zpool.ca live status JSON (no `quark` entry)](https://www.zpool.ca/api/status) - [Quark blockchain explorer — live chain, `getdifficulty` API](https://chainz.cryptoid.info/qrk/)
dobbscoin commented 2026-06-17 01:18:34 +00:00

Issue #6 comment draft — post-canon permanent-security decision

Drafted 2026-06-16 by Operator for Bob. Public-appropriate distillation of
post-canon-mining-security.md (the internal memo) — all key-custody specifics
stripped. Paste into GitHub issue #6 when ratified.


Raising the post-canon permanent-security decision before the impl runway opens (~h1,030,000)

Cross-referencing this against the OFFSIG window and what's already in the tree, three things worth settling before code starts:

1. Phase 1 alone is not a 51% defense — promote Phase 2 to the committed answer.

Phase 1 self-rolling checkpoints are per-node: each node checkpoints its own accepted chain at ROLLING_DEPTH. That's exactly right for the stated threat model (fresh-sync / offline-rejoin fed a poisoned deep history). But it does not stop a real-time majority attacker — a node that syncs the attacker's longer chain will happily self-checkpoint that chain. On a ~16 MH/s Quark chain with a named repeat attacker (#699), a rented-hash deep reorg in the 100 < d < sync-depth band is the actual post-window risk, and only network-agreed (broadcast) checkpoints close it.

The good news: the CSyncCheckpoint / ValidateSyncCheckpoint machinery (ppcoin/ACP-style, centrally broadcast) is already vendored in checkpoints.cpp — it just needs wiring to a Conclave checkpoint key (same custody model as OFFSIG) and a broadcast cadence. Proposal: move "Phase 2 (centralized override / emergency consensus)" out of deferred and make it the permanent post-canon scheme. Phase 1 ships as-specced; Phase 2 is the 51% answer. This is the Feathercoin-ACP precedent — small chain, post-51%-attack, keeps mining permissionless while bounding reorgs.

2. There's a ~3.4-day unguarded gap between the window closing and rolling activation.

OFFSIG closes at 1,050,666; HARDFORK_ROLLING_CKPT_MAIN_OFF = 1,055,555. That's 4,889 blocks (~3.4 days) of fully-open mining with no rolling guard — only MAX_REORG_DEPTH=100 near-tip. Two ways to close it:

  • (a) pull rolling activation forward to ~1,050,667 (immediately post-window), or
  • (b) have the Phase-2 broadcast checkpoints live before the window closes.

Recommend (b): ACP broadcast is an overlay, not a hardfork, so it can be live early and cover the gap without touching the 1,055,555 consensus constant. (a) is the cheap fallback if Phase 2 slips.

3. AuxPoW is a long-term complement, not a deadline item.

Merge-mining is the real hashrate-borrowing answer eventually (cf. (BOB)'s scrypt AuxPoW), but Quark's merge-mining pool is thin, it needs miner/pool adoption, and it provides no finality on its own — not something to stand up reliably inside this runway. Track it separately; it composes with checkpoints later.

Not recommended: permanently extending OFFSIG (centralization regression — defeats the permissionless-network goal), or accept-it (untenable with a live reclamation/bridge economy and a named attacker).

Suggested sequence: ratify now → stand up the Phase-2 checkpoint broadcaster (dark) before h1,050,666 → implement/test Phase 1 in the 1,030,000→1,055,555 runway (testnet first, LWMA-3 process) → 1,055,555 rolling activates with ACP already carrying finality.

# Issue #6 comment draft — post-canon permanent-security decision > Drafted 2026-06-16 by Operator for Bob. Public-appropriate distillation of > `post-canon-mining-security.md` (the internal memo) — all key-custody specifics > stripped. Paste into GitHub issue #6 when ratified. --- **Raising the post-canon permanent-security decision before the impl runway opens (~h1,030,000)** Cross-referencing this against the OFFSIG window and what's already in the tree, three things worth settling before code starts: **1. Phase 1 alone is not a 51% defense — promote Phase 2 to the committed answer.** Phase 1 self-rolling checkpoints are *per-node*: each node checkpoints its own accepted chain at `ROLLING_DEPTH`. That's exactly right for the stated threat model (fresh-sync / offline-rejoin fed a poisoned deep history). But it does **not** stop a real-time majority attacker — a node that syncs the attacker's longer chain will happily self-checkpoint *that* chain. On a ~16 MH/s Quark chain with a named repeat attacker (#699), a rented-hash deep reorg in the `100 < d < sync-depth` band is the actual post-window risk, and only **network-agreed (broadcast) checkpoints** close it. The good news: the `CSyncCheckpoint` / `ValidateSyncCheckpoint` machinery (ppcoin/ACP-style, centrally broadcast) is **already vendored in `checkpoints.cpp`** — it just needs wiring to a Conclave checkpoint key (same custody model as OFFSIG) and a broadcast cadence. Proposal: move "Phase 2 (centralized override / emergency consensus)" out of *deferred* and make it the permanent post-canon scheme. Phase 1 ships as-specced; Phase 2 is the 51% answer. This is the Feathercoin-ACP precedent — small chain, post-51%-attack, keeps mining permissionless while bounding reorgs. **2. There's a ~3.4-day unguarded gap between the window closing and rolling activation.** OFFSIG closes at **1,050,666**; `HARDFORK_ROLLING_CKPT_MAIN_OFF = 1,055,555`. That's **4,889 blocks (~3.4 days) of fully-open mining with no rolling guard** — only `MAX_REORG_DEPTH=100` near-tip. Two ways to close it: - (a) pull rolling activation forward to ~1,050,667 (immediately post-window), or - (b) have the Phase-2 broadcast checkpoints live *before* the window closes. Recommend (b): ACP broadcast is an overlay, not a hardfork, so it can be live early and cover the gap without touching the 1,055,555 consensus constant. (a) is the cheap fallback if Phase 2 slips. **3. AuxPoW is a long-term complement, not a deadline item.** Merge-mining is the real hashrate-borrowing answer eventually (cf. (BOB)'s scrypt AuxPoW), but Quark's merge-mining pool is thin, it needs miner/pool adoption, and it provides no finality on its own — not something to stand up reliably inside this runway. Track it separately; it composes with checkpoints later. **Not recommended:** permanently extending OFFSIG (centralization regression — defeats the permissionless-network goal), or accept-it (untenable with a live reclamation/bridge economy and a named attacker). **Suggested sequence:** ratify now → stand up the Phase-2 checkpoint broadcaster (dark) before h1,050,666 → implement/test Phase 1 in the 1,030,000→1,055,555 runway (testnet first, LWMA-3 process) → 1,055,555 rolling activates with ACP already carrying finality.
dobbscoin commented 2026-06-18 02:41:59 +00:00

ok phase 1 is in. six commits on feat/v2.0.x-rc-bipsoft, tip c2a67a64. joins #32 / #33 / #34 / #39 in the same soak, all activating at mainnet h=1,055,555.

couple of things ended up different from the spec.

went with a two-map structure instead of mutating mapCheckpoints in place. same external behavior — CheckBlock / GetTotalBlocksEstimate / GetLastCheckpoint all consult both layers — but the static map stays immutable and the runtime rolling map is its own thing. static entries can't get accidentally dropped by GC, clearrollingcheckpoints only touches the rolling layer, on-disk format is unambiguous. felt cleaner than the const_cast gymnastics the literal spec would have needed.

-checkpoints and -rollingcheckpoints are independent flags. testnet caught this one on first deploy — we run testnet with checkpoints=0 as a workaround for a stale mapCheckpointsTestnet entry, and that was silently disabling the rolling layer too. fix: -checkpoints=0 skips dev-baked static checks, -rollingcheckpoints=0 skips the daemon's auto-rollforward, you can pick either independently.

regtest covers pre-activation void, activation boundary, continued rolling, restart persistence, runtime toggle, and clear-below-height with disk rewrite. all PASS at qa/rpc-tests/rolling_checkpoints.py.

live on the testnet soak now. restarted at h=2142, rolling map locked h=1119 immediately (= 2142 − 1023), mining advanced to h=2145 / locked h=1122. activation heights are 1,055,555 mainnet, 100 testnet, 110 regtest.

phase 2 (Conclave-signed override) still deferred per the issue body. opens as separate work once phase 1 settles.

merges to main with the rest of the bipsoft bundle at freeze-end ~2026-07-19.

ok phase 1 is in. six commits on `feat/v2.0.x-rc-bipsoft`, tip `c2a67a64`. joins #32 / #33 / #34 / #39 in the same soak, all activating at mainnet `h=1,055,555`. couple of things ended up different from the spec. went with a two-map structure instead of mutating `mapCheckpoints` in place. same external behavior — `CheckBlock` / `GetTotalBlocksEstimate` / `GetLastCheckpoint` all consult both layers — but the static map stays immutable and the runtime rolling map is its own thing. static entries can't get accidentally dropped by GC, `clearrollingcheckpoints` only touches the rolling layer, on-disk format is unambiguous. felt cleaner than the const_cast gymnastics the literal spec would have needed. `-checkpoints` and `-rollingcheckpoints` are independent flags. testnet caught this one on first deploy — we run testnet with `checkpoints=0` as a workaround for a stale `mapCheckpointsTestnet` entry, and that was silently disabling the rolling layer too. fix: `-checkpoints=0` skips dev-baked static checks, `-rollingcheckpoints=0` skips the daemon's auto-rollforward, you can pick either independently. regtest covers pre-activation void, activation boundary, continued rolling, restart persistence, runtime toggle, and clear-below-height with disk rewrite. all PASS at `qa/rpc-tests/rolling_checkpoints.py`. live on the testnet soak now. restarted at h=2142, rolling map locked h=1119 immediately (= 2142 − 1023), mining advanced to h=2145 / locked h=1122. activation heights are 1,055,555 mainnet, 100 testnet, 110 regtest. phase 2 (Conclave-signed override) still deferred per the issue body. opens as separate work once phase 1 settles. merges to main with the rest of the bipsoft bundle at freeze-end ~2026-07-19.
dobbscoin commented 2026-06-18 16:15:12 +00:00

Phase 1 implemented, regtest passed, in soak.

The six commits landing in c2a67a64 carry out the design in the issue body:

  • fd68873d — activation constants in src/pow.h
  • 5b87f88d — scaffolding in src/checkpoints.{h,cpp}
  • 66591fe7 — MaybeRollForward wired into ConnectTip + startup load in init.cpp
  • 78bbff90 — RPCs getrollingcheckpoints, clearrollingcheckpoints, setrollingcheckpointsenabled
  • 2fafcc1e — qa/rpc-tests/rolling_checkpoints.py (278 LOC, regtest harness)
  • c2a67a64 — decouple -rollingcheckpoints from the -checkpoints master flag

All six phases of the regtest pass against a fresh build from branch tip:

  1. Pre-activation tip (h=1132) → rolling map empty
  2. Cross activation boundary at h=1133 → first entry locks at h=110
  3. Continued rolling — h=1138 has heights 110..115
  4. Persistence across daemon restart (disk round-trip)
  5. Runtime toggle (setrollingcheckpointsenabled false/true)
  6. clearrollingcheckpoints below_height purges memory + disk, survives restart

Folded into v2.0.x-rc-bipsoft-rc2 (tagged from c2a67a64) for the next testnet soak window alongside #32/#33/#34. Mainnet activation height stays at 1,055,555; rolling depth 1023 means the first lock fires at h=1,056,578. The Lord begins to remember His own history.

Phase 1 implemented, regtest passed, in soak. The six commits landing in `c2a67a64` carry out the design in the issue body: - `fd68873d` — activation constants in `src/pow.h` - `5b87f88d` — scaffolding in `src/checkpoints.{h,cpp}` - `66591fe7` — `MaybeRollForward` wired into `ConnectTip` + startup load in `init.cpp` - `78bbff90` — RPCs `getrollingcheckpoints`, `clearrollingcheckpoints`, `setrollingcheckpointsenabled` - `2fafcc1e` — `qa/rpc-tests/rolling_checkpoints.py` (278 LOC, regtest harness) - `c2a67a64` — decouple `-rollingcheckpoints` from the `-checkpoints` master flag All six phases of the regtest pass against a fresh build from branch tip: 1. Pre-activation tip (h=1132) → rolling map empty 2. Cross activation boundary at h=1133 → first entry locks at h=110 3. Continued rolling — h=1138 has heights 110..115 4. Persistence across daemon restart (disk round-trip) 5. Runtime toggle (`setrollingcheckpointsenabled false/true`) 6. `clearrollingcheckpoints below_height` purges memory + disk, survives restart Folded into `v2.0.x-rc-bipsoft-rc2` (tagged from `c2a67a64`) for the next testnet soak window alongside #32/#33/#34. Mainnet activation height stays at **1,055,555**; rolling depth 1023 means the first lock fires at h=1,056,578. *The Lord begins to remember His own history.*
dobbscoin commented 2026-07-27 21:51:05 +00:00

Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block 00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c.

The first rolling locks were verified byte-identical across all four production fleet nodes, each persisting its own rolling_checkpoints.dat. The map has advanced per-block ever since — getrollingcheckpoints on a production node today reports enabled: true, activation_height: 1055555, depth: 1023, with the window trailing the tip exactly as specified. Combined with MAX_REORG_DEPTH=100, buried history is now pinned per-node: a deep secret-chain rewrite of the 2018 shape is consensus-impossible.

Implementation notes vs. the original spec, for the record: shipped as a two-map design (static mapCheckpoints + runtime mapCheckpointsRolling) rather than a single mutable map, and -checkpoints / -rollingcheckpoints are semantically orthogonal flags — a coupling bug the testnet caught on first deploy. Three operator RPCs (getrollingcheckpoints, clearrollingcheckpoints, setrollingcheckpointsenabled) ship alongside.

Phase 1 complete and shipped in v2.0.9-Eldersign; the Phase-2 broadcast layer (#40, merged) awaits first-light with v2.1.0. That is not dead which can eternal lie — but from this height forward, neither can it be rewritten. Closing.

Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block `00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c`. The first rolling locks were verified **byte-identical across all four production fleet nodes**, each persisting its own `rolling_checkpoints.dat`. The map has advanced per-block ever since — `getrollingcheckpoints` on a production node today reports `enabled: true`, `activation_height: 1055555`, `depth: 1023`, with the window trailing the tip exactly as specified. Combined with `MAX_REORG_DEPTH=100`, buried history is now pinned per-node: a deep secret-chain rewrite of the 2018 shape is consensus-impossible. Implementation notes vs. the original spec, for the record: shipped as a **two-map design** (static `mapCheckpoints` + runtime `mapCheckpointsRolling`) rather than a single mutable map, and `-checkpoints` / `-rollingcheckpoints` are semantically orthogonal flags — a coupling bug the testnet caught on first deploy. Three operator RPCs (`getrollingcheckpoints`, `clearrollingcheckpoints`, `setrollingcheckpointsenabled`) ship alongside. Phase 1 complete and shipped in v2.0.9-Eldersign; the Phase-2 broadcast layer (#40, merged) awaits first-light with v2.1.0. That is not dead which can eternal lie — but from this height forward, neither can it be rewritten. Closing.
dobbscoin closed this issue 2026-07-27 21:51:06 +00:00
Sign in to join this conversation.
No labels
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
SubGeniusFinance/Offerings-to-Cthulhu#6
No description provided.