Files
triangles_v5/docs/snapshot-publisher.md
T
SamiAhmed7777 8598cfa781 feat(bootstrap): RPC-driven trusted snapshot publisher rotation (v6.1.8) (#26)
Design A: single-slot runtime override via RPC. The previous publisher
is dropped atomically on every set. The built-in fallback list
(TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX, Sami's legacy key) is always
consulted if no runtime override is set, so a fresh daemon still
verifies old snapshots without operator intervention.

New RPCs:
- settrustedv2snapshotpublisher <address>
- gettrustedv2snapshotpublisher
- unsettrustedv2snapshotpublisher

Persistence: <datadir>/snapshot-publisher.json (plain JSON).
Loaded at startup in init.cpp before any snapshot verification.

Files:
  src/bootstrap.cpp          (+116 / -8)  Replace hardcoded list with single-slot + fallback
  src/bootstrap.h            (+21)        Declare new Bootstrap:: functions
  src/init.cpp               (+3)         LoadTrustedSnapshotPublisher() at startup
  src/rpcblockchain.cpp      (+89)        Three new RPC function bodies
  src/rpcblockchain.cpp      (+1)         #include "bootstrap.h"
  src/trianglesrpc.cpp       (+3)         Register three new commands
  src/trianglesrpc.h         (+3)         extern declarations
  README.md                  (+30)        New 'Trusted Snapshot Publisher' sections
  TRIANGLES-RPC-COMMANDS.md  (+3)         Three new rows in Blockchain table
  docs/snapshot-publisher.md (new, +240)  Full operator handoff guide

Co-authored-by: Krystie <krystie@openclaw.local>
2026-07-10 17:29:15 -07:00

7.8 KiB

Trusted Snapshot Publisher — Operator Guide

This document explains how the trusted snapshot publisher mechanism works in Triangles and how to rotate the publisher without rebuilding the daemon. It is written for the person who operates the Triangles network after Sami — whoever that turns out to be.

Background

The Triangles daemon verifies that any UTXO snapshot it loads was signed by a trusted publisher. This prevents a malicious snapshot file from tricking a node into accepting a fake chain state.

In versions before v6.1.8, the trusted publisher list was hardcoded in the binary. To rotate keys, the daemon had to be rebuilt and re-released. That was bad for handover.

Starting with v6.1.8, the daemon supports a runtime-configurable single-slot trusted publisher via RPC. The compiled-in fallback list is still consulted if no runtime publisher is set, so a fresh daemon never fails to verify an old snapshot.

The model — Design A (single-slot, auto-replace)

  • At most ONE runtime publisher exists at any time.
  • Calling settrustedv2snapshotpublisher <addr> atomically replaces the current publisher. The previous one is dropped immediately. There is no grace period, no retirement list, no rollback path. Pure single-slot.
  • The active publisher is persisted to <datadir>/snapshot-publisher.json, so it survives daemon restarts.
  • The built-in fallback list (read-only, compiled into the binary) is consulted only if no runtime publisher is set. That list contains:
    • TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX — Sami's legacy snapshot publisher key (the original, used from v6.1.5 through v6.1.7).

The three RPCs

settrustedv2snapshotpublisher <address>

Atomically replaces the active trusted publisher. The previous publisher is dropped immediately. The new publisher is persisted to <datadir>/snapshot-publisher.json so the choice survives restarts.

triangles-cli settrustedv2snapshotpublisher TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH

Result:

{
  "previous": "TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX",
  "current":  "TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH"
}

The previous field is empty if no runtime publisher was set before.

gettrustedv2snapshotpublisher

Returns the currently active runtime publisher.

triangles-cli gettrustedv2snapshotpublisher

Result:

{
  "active": "TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH",
  "has_runtime_override": true
}

If has_runtime_override is false, only the built-in fallback list is consulted. The fallback currently contains TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX.

unsettrustedv2snapshotpublisher

Clears the runtime override. Reverts to the built-in fallback list. Also removes <datadir>/snapshot-publisher.json.

triangles-cli unsettrustedv2snapshotpublisher

Use this if you want to "go back to the legacy trusted signer" without a rebuild.

Common rotation scenarios

Rotate to a new key (forward rotation)

  1. Generate a new key in the wallet:
    triangles-cli getnewaddress
    # returns: TNewAddressHere...
    
  2. (Optional but recommended) Label it so you remember its role:
    triangles-cli setaccount TNewAddressHere... "snapshot publisher"
    
  3. Set it as the trusted publisher:
    triangles-cli settrustedv2snapshotpublisher TNewAddressHere...
    
  4. Verify:
    triangles-cli gettrustedv2snapshotpublisher
    
    Should show active: TNewAddressHere....

Old publisher is dropped immediately. New one is in effect for this daemon and any daemon that syncs from <datadir>/snapshot-publisher.json.

Roll back to the legacy publisher

If the new key is lost / compromised / you just want to revert:

triangles-cli unsettrustedv2snapshotpublisher

This reverts to the built-in fallback (TG8f76ykt...). No rebuild required. The legacy address will continue to verify any snapshot that was signed before your rotation.

Rotate during a handover (publisher A hands off to publisher B)

  1. Publisher B installs v6.1.8+ daemon.
  2. Publisher B sets themselves as the trusted publisher:
    triangles-cli settrustedv2snapshotpublisher TBsAddress...
    
  3. Publisher B signs a new snapshot with their key (see publishcheckpoint in TRIANGLES-RPC-COMMANDS.md).
  4. Publisher A can leave the network; their key is no longer trusted on any node that has called settrustedv2snapshotpublisher.

Note: because Design A auto-drops the previous publisher, only one operator can publish at a time. If you need overlap (both A and B publishing during a transition), that requires Design B (multi-slot with grace period) — not supported in v6.1.8. Contact Sami for the upgrade path.

Files

Path Purpose
<datadir>/snapshot-publisher.json Runtime publisher override. Plain JSON. Inspectable with cat.
<datadir>/wallet.dat Must contain the privkey for the active publisher, otherwise publishcheckpoint will fail at signing time. (Trust is governed by the override; signing is governed by the wallet.)

<datadir>/snapshot-publisher.json format

{
  "address": "TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH",
  "set_at": 1752168000,
  "note": "Set via triangles-cli settrustedv2snapshotpublisher. Replace atomically; previous publisher is dropped."
}

set_at is the Unix timestamp when the RPC was last called. note is informational only.

Recovery if RPC fails

If for some reason the runtime override can't be persisted (e.g. JSON write fails), the RPC returns a warning but the in-memory change is already live for the current session. To check:

triangles-cli gettrustedv2snapshotpublisher

If active is set, you're good for the current session. The next daemon restart will lose it unless snapshot-publisher.json exists. Inspect it manually:

cat ~/.triangles/snapshot-publisher.json

If the file doesn't exist but you need the override to survive restart, hand-write it:

{
  "address": "TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH",
  "set_at": 1752168000,
  "note": "Hand-set; rotate via triangles-cli settrustedv2snapshotpublisher."
}

The daemon reads this file at startup. Address must be 34 chars and start with T. Anything else is logged and ignored.

When you DO need a rebuild

  • Adding a new entry to the built-in fallback list (the read-only list compiled into the binary). Edit BUILTIN_TRUSTED_SNAPSHOT_SIGNERS[] in src/bootstrap.cpp, rebuild, release. This is only needed if you want a publisher to be trusted without any operator running the RPC.
  • Changing the RPC names or argument shapes. Edit source, rebuild.

For everyday "I want to add or rotate a trusted publisher," the RPC is enough. Don't rebuild.

Why "single-slot, no grace period"

Sami asked for it explicitly when designing the operator-experience for this feature. The trade-off: if the active key is lost or compromised, there's no automatic fallback. The operator must either re-add the previous key (which requires they kept the JSON file or remember the address) or rebuild with the new key in BUILTIN_TRUSTED_SNAPSHOT_SIGNERS[].

If this trade-off becomes painful — for example if multiple operators need to publish during a handover — the alternative is Design B (multi-slot with grace period). That's a one-day patch on top of this one. Ask Sami for the upgrade.

Versioning

This feature is introduced in v6.1.8. Daemons older than v6.1.8 still use the hardcoded TG8f76ykt... only — they cannot use the new key until they upgrade.

For the publishing side (signing snapshots, not verifying them), see:

  • publishcheckpoint <interval> <signing_address> <output_path> — builds and signs a checkpoint document.
  • gencheckpoints — generates raw checkpoint data without signing.
  • getcheckpoint — returns the current synchronized checkpoint.

See TRIANGLES-RPC-COMMANDS.md for full details on those.