From 8598cfa7817078e59fb43240dd4f3168afd8fe5a Mon Sep 17 00:00:00 2001
From: SamiAhmed7777 <79177212+SamiAhmed7777@users.noreply.github.com>
Date: Fri, 10 Jul 2026 17:29:15 -0700
Subject: [PATCH] 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
- gettrustedv2snapshotpublisher
- unsettrustedv2snapshotpublisher
Persistence: /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
---
README.md | 30 +++++
TRIANGLES-RPC-COMMANDS.md | 3 +
docs/snapshot-publisher.md | 240 +++++++++++++++++++++++++++++++++++++
src/bootstrap.cpp | 124 ++++++++++++++++++-
src/bootstrap.h | 21 ++++
src/init.cpp | 3 +
src/rpcblockchain.cpp | 89 ++++++++++++++
src/trianglesrpc.cpp | 5 +-
src/trianglesrpc.h | 3 +
9 files changed, 511 insertions(+), 7 deletions(-)
create mode 100644 docs/snapshot-publisher.md
diff --git a/README.md b/README.md
index c2b3900..499f6c5 100644
--- a/README.md
+++ b/README.md
@@ -175,6 +175,29 @@ Check staking status:
trianglesd getstakinginfo
```
+### Trusted Snapshot Publisher (UTXO Snapshots)
+
+The daemon verifies that any UTXO snapshot it loads was signed by a
+**trusted publisher**. Starting with v6.1.8, the trusted publisher can
+be rotated at runtime via RPC — no rebuild required. The compiled-in
+fallback (`TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX`, Sami's legacy key)
+remains in effect if no runtime override is set.
+
+```bash
+# Rotate to a new publisher
+trianglesd settrustedv2snapshotpublisher TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH
+
+# Check current publisher
+trianglesd gettrustedv2snapshotpublisher
+
+# Revert to the compiled-in fallback
+trianglesd unsettrustedv2snapshotpublisher
+```
+
+The model is single-slot: calling `settrustedv2snapshotpublisher`
+atomically drops the previous publisher. See `docs/snapshot-publisher.md`
+for the full operator guide.
+
### Encrypted Messaging
Send and receive encrypted messages between wallet addresses:
@@ -234,6 +257,13 @@ Then set `externalip=` in `triangles.conf`.
- `smsglocalkeys` - List messaging-enabled addresses
- `smsgscanchain` - Scan blockchain for public keys
+### Trusted Snapshot Publisher (v6.1.8+)
+- `settrustedv2snapshotpublisher ` - Atomically replace the trusted snapshot publisher (previous one dropped immediately). Persists to `/snapshot-publisher.json`.
+- `gettrustedv2snapshotpublisher` - Returns the currently active runtime publisher and whether a runtime override is in effect.
+- `unsettrustedv2snapshotpublisher` - Clear the runtime override and revert to the compiled-in fallback list.
+
+See `docs/snapshot-publisher.md` for the full operator guide.
+
## Chain History
- **July 16, 2014** - Genesis block
diff --git a/TRIANGLES-RPC-COMMANDS.md b/TRIANGLES-RPC-COMMANDS.md
index b21e159..d873854 100644
--- a/TRIANGLES-RPC-COMMANDS.md
+++ b/TRIANGLES-RPC-COMMANDS.md
@@ -31,6 +31,9 @@ Triangles is a Tor-only PoS cryptocurrency. PoW ended at block 9000; from block
| `getrawmempool` | | Returns all transaction IDs currently in the mempool. |
| `getcheckpoint` | | Returns info about the current synchronized checkpoint. |
| `getchaintips` | | Returns info about all known chain tips (forks). |
+| `settrustedv2snapshotpublisher` | `` | Atomically replaces the trusted snapshot publisher. The previous publisher is dropped immediately (no grace period). The new publisher is persisted to `/snapshot-publisher.json`. Returns `{ previous, current }`. See `docs/snapshot-publisher.md`. |
+| `gettrustedv2snapshotpublisher` | | Returns the currently active trusted snapshot publisher and whether a runtime override is in effect. Returns `{ active, has_runtime_override }`. |
+| `unsettrustedv2snapshotpublisher` | | Clears the runtime trusted snapshot publisher override. Reverts to the built-in fallback list (compiled in). Removes `/snapshot-publisher.json`. |
| `invalidateblock` | `` | Permanently marks a block as invalid and rewinds the chain past it. |
| `reconsiderblock` | `` | Removes the invalid mark from a previously invalidated block. |
| `recalculatesupply` | | Recalculates money supply by summing all UTXOs. Updates the stored value at the chain tip and persists to disk. Returns old/new supply and difference. |
diff --git a/docs/snapshot-publisher.md b/docs/snapshot-publisher.md
new file mode 100644
index 0000000..916822a
--- /dev/null
+++ b/docs/snapshot-publisher.md
@@ -0,0 +1,240 @@
+# 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 ` **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
+ `/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 `
+
+Atomically replaces the active trusted publisher. The previous
+publisher is dropped immediately. The new publisher is persisted to
+`/snapshot-publisher.json` so the choice survives restarts.
+
+```
+triangles-cli settrustedv2snapshotpublisher TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH
+```
+
+Result:
+```json
+{
+ "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:
+```json
+{
+ "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 `/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 `/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 |
+|---|---|
+| `/snapshot-publisher.json` | Runtime publisher override. Plain JSON. Inspectable with `cat`. |
+| `/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.) |
+
+### `/snapshot-publisher.json` format
+
+```json
+{
+ "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:
+```json
+{
+ "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.
+
+## Related RPCs
+
+For the publishing side (signing snapshots, not verifying them),
+see:
+
+- `publishcheckpoint ` —
+ 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.
\ No newline at end of file
diff --git a/src/bootstrap.cpp b/src/bootstrap.cpp
index fbd0f9b..82cbe3a 100644
--- a/src/bootstrap.cpp
+++ b/src/bootstrap.cpp
@@ -775,16 +775,128 @@ namespace {
// Trusted signer addresses for snapshot manifests. A snapshot is accepted
// iff its manifest's signing_address matches one of these AND its signature
// verifies under Triangles' compact-message protocol.
-static const char* TRUSTED_SNAPSHOT_SIGNERS[] = {
- "TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX", // Sami's snapshot publisher key
+//
+// Design A: single-slot runtime override via RPC. The previous publisher
+// is dropped atomically on every set. The built-in fallback below is
+// always consulted if no runtime override is set, so a fresh daemon still
+// verifies old snapshots without operator intervention.
+
+// Built-in fallback (read-only, compiled in).
+static const char* BUILTIN_TRUSTED_SNAPSHOT_SIGNERS[] = {
+ "TG8f76yktTxDrT7JJymY3wVAusXiD3fVvX", // Sami's legacy snapshot publisher key
};
-static const size_t NUM_TRUSTED_SNAPSHOT_SIGNERS =
- sizeof(TRUSTED_SNAPSHOT_SIGNERS) / sizeof(TRUSTED_SNAPSHOT_SIGNERS[0]);
+static const size_t NUM_BUILTIN_TRUSTED_SNAPSHOT_SIGNERS =
+ sizeof(BUILTIN_TRUSTED_SNAPSHOT_SIGNERS) / sizeof(BUILTIN_TRUSTED_SNAPSHOT_SIGNERS[0]);
+
+// Runtime override. Empty string = no override, use built-in fallback.
+static std::string g_activeTrustedSnapshotPublisher;
+static std::mutex g_trustedPublisherMutex;
+static const char* SNAPSHOT_PUBLISHER_FILE = "snapshot-publisher.json";
+
+std::string GetActiveTrustedSnapshotPublisher()
+{
+ std::lock_guard lock(g_trustedPublisherMutex);
+ return g_activeTrustedSnapshotPublisher;
+}
+
+static void SetActiveTrustedSnapshotPublisherUnlocked(const std::string& addr)
+{
+ g_activeTrustedSnapshotPublisher = addr;
+}
+
+// Load runtime override from /snapshot-publisher.json.
+// Called once at startup from init.cpp.
+void LoadTrustedSnapshotPublisher()
+{
+ fs::path filePath = GetDataDir(true) / SNAPSHOT_PUBLISHER_FILE;
+ if (!fs::exists(filePath))
+ return;
+
+ std::ifstream f(filePath.string().c_str());
+ if (!f) return;
+
+ std::stringstream ss; ss << f.rdbuf();
+ std::string json = ss.str();
+
+ // Minimal JSON parse: "address":""
+ size_t keyPos = json.find("\"address\"");
+ if (keyPos == std::string::npos) return;
+ size_t colonPos = json.find(':', keyPos);
+ if (colonPos == std::string::npos) return;
+ size_t q1 = json.find('"', colonPos);
+ if (q1 == std::string::npos) return;
+ size_t q2 = json.find('"', q1 + 1);
+ if (q2 == std::string::npos) return;
+
+ std::string addr = json.substr(q1 + 1, q2 - q1 - 1);
+ if (addr.size() != 34 || addr[0] != 'T') {
+ printf("Bootstrap: snapshot-publisher.json contains invalid address '%s', ignoring\n",
+ addr.c_str());
+ return;
+ }
+
+ {
+ std::lock_guard lock(g_trustedPublisherMutex);
+ SetActiveTrustedSnapshotPublisherUnlocked(addr);
+ }
+ printf("Bootstrap: loaded trusted snapshot publisher override: %s\n", addr.c_str());
+}
+
+static bool PersistTrustedSnapshotPublisher(const std::string& addr)
+{
+ fs::path filePath = GetDataDir(true) / SNAPSHOT_PUBLISHER_FILE;
+ std::ofstream f(filePath.string().c_str(), std::ios::trunc);
+ if (!f) return false;
+ f << "{\n"
+ << " \"address\": \"" << addr << "\",\n"
+ << " \"set_at\": " << GetTime() << ",\n"
+ << " \"note\": \"Set via triangles-cli settrustedv2snapshotpublisher. "
+ << "Replace atomically; previous publisher is dropped.\"\n"
+ << "}\n";
+ return f.good();
+}
+
+bool SetTrustedSnapshotPublisher(const std::string& addr, std::string& strError)
+{
+ if (addr.size() != 34 || addr[0] != 'T') {
+ strError = "settrustedv2snapshotpublisher: invalid address format (expected 34-char T-address)";
+ return false;
+ }
+ {
+ std::lock_guard lock(g_trustedPublisherMutex);
+ SetActiveTrustedSnapshotPublisherUnlocked(addr);
+ }
+ if (!PersistTrustedSnapshotPublisher(addr)) {
+ strError = "settrustedv2snapshotpublisher: warning, could not persist to "
+ "snapshot-publisher.json (in-memory change is live for this session)";
+ return true;
+ }
+ return true;
+}
+
+bool UnsetTrustedSnapshotPublisher(std::string& strError)
+{
+ {
+ std::lock_guard lock(g_trustedPublisherMutex);
+ SetActiveTrustedSnapshotPublisherUnlocked(std::string());
+ }
+ fs::path filePath = GetDataDir(true) / SNAPSHOT_PUBLISHER_FILE;
+ fs::remove(filePath);
+ return true;
+}
bool IsTrustedSnapshotSigner(const std::string& addr)
{
- for (size_t i = 0; i < NUM_TRUSTED_SNAPSHOT_SIGNERS; ++i)
- if (addr == TRUSTED_SNAPSHOT_SIGNERS[i])
+ // 1. Runtime override (set via RPC).
+ {
+ std::lock_guard lock(g_trustedPublisherMutex);
+ if (!g_activeTrustedSnapshotPublisher.empty() &&
+ addr == g_activeTrustedSnapshotPublisher)
+ return true;
+ }
+ // 2. Built-in fallback (compiled in, read-only).
+ for (size_t i = 0; i < NUM_BUILTIN_TRUSTED_SNAPSHOT_SIGNERS; ++i)
+ if (addr == BUILTIN_TRUSTED_SNAPSHOT_SIGNERS[i])
return true;
return false;
}
diff --git a/src/bootstrap.h b/src/bootstrap.h
index 550dc44..07ee873 100644
--- a/src/bootstrap.h
+++ b/src/bootstrap.h
@@ -73,6 +73,27 @@ namespace Bootstrap {
ProgressCallback progressFn,
std::string& strError);
+ // ===================================================================
+ // Trusted snapshot publisher — RPC-driven single-slot rotation
+ // ===================================================================
+ // Returns the currently active trusted publisher, or empty string if
+ // only the built-in fallback is in effect.
+ std::string GetActiveTrustedSnapshotPublisher();
+
+ // Atomically replaces the active publisher. The previous one is dropped
+ // immediately (Design A: single-slot, no grace period). Persists to
+ // /snapshot-publisher.json so the choice survives restarts.
+ bool SetTrustedSnapshotPublisher(const std::string& addr,
+ std::string& strError);
+
+ // Clears the runtime override and reverts to the built-in fallback
+ // list. Also removes snapshot-publisher.json from disk.
+ bool UnsetTrustedSnapshotPublisher(std::string& strError);
+
+ // Called once at daemon startup (from init.cpp) to load any persisted
+ // runtime override.
+ void LoadTrustedSnapshotPublisher();
+
} // namespace Bootstrap
#endif // TRIANGLES_BOOTSTRAP_H
diff --git a/src/init.cpp b/src/init.cpp
index 9b89670..0dd913d 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1191,6 +1191,9 @@ bool AppInit2()
bool noBootstrap = GetBoolArg("-nobootstrap", false);
bool snapshotMode = GetBoolArg("-snapshot", true);
fs::path dataPath = GetDataDir();
+ // Load any runtime trusted snapshot publisher override that was
+ // persisted by a previous settrustedv2snapshotpublisher call.
+ Bootstrap::LoadTrustedSnapshotPublisher();
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
if (needsBootstrap && !noBootstrap) {
diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp
index c4d8084..fc07be9 100644
--- a/src/rpcblockchain.cpp
+++ b/src/rpcblockchain.cpp
@@ -13,6 +13,7 @@
#include "utxosnapshot.h"
#include "checkpointpublisher.h"
#include "wallet.h"
+#include "bootstrap.h"
#include
@@ -1322,3 +1323,91 @@ Value dumputxoset(const Array& params, bool fHelp)
return result;
}
+
+// ============================================================================
+// Trusted snapshot publisher RPCs (Design A: single-slot rotation)
+// ============================================================================
+//
+// settrustedv2snapshotpublisher
+// - Atomically replaces the active trusted snapshot publisher.
+// - The previous publisher is dropped immediately (no grace period).
+// - The new publisher is persisted to /snapshot-publisher.json
+// so the choice survives daemon restarts.
+//
+// gettrustedv2snapshotpublisher
+// - Returns the currently active runtime override.
+// - Empty string means no runtime override; built-in fallback list is
+// the source of truth (which contains "TG8f76ykt...").
+//
+// unsettrustedv2snapshotpublisher
+// - Clears the runtime override.
+// - The built-in fallback list (read-only, compiled in) becomes the
+// source of truth again.
+// - Removes /snapshot-publisher.json.
+Value settrustedv2snapshotpublisher(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() != 1)
+ throw runtime_error(
+ "settrustedv2snapshotpublisher \n"
+ "Atomically replace the trusted snapshot publisher.\n"
+ "The previous publisher is dropped immediately (no grace period).\n"
+ "The new publisher is persisted to /snapshot-publisher.json.\n"
+ "\nArguments:\n"
+ "1. address (string, required) Triangles T-address (34 chars, starts with 'T')\n"
+ "\nResult:\n"
+ "{ previous: 'T...', current: 'T...' } (previous is empty if first set)\n"
+ "\nExample:\n"
+ " triangles-cli settrustedv2snapshotpublisher TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH");
+
+ std::string addr = params[0].get_str();
+ std::string previous = Bootstrap::GetActiveTrustedSnapshotPublisher();
+ std::string err;
+ if (!Bootstrap::SetTrustedSnapshotPublisher(addr, err)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, err);
+ }
+ Object result;
+ result.push_back(Pair("previous", previous));
+ result.push_back(Pair("current", addr));
+ if (!err.empty())
+ result.push_back(Pair("warning", err));
+ return result;
+}
+
+Value gettrustedv2snapshotpublisher(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() != 0)
+ throw runtime_error(
+ "gettrustedv2snapshotpublisher\n"
+ "Returns the currently active trusted snapshot publisher.\n"
+ "Empty string means no runtime override is set; the built-in\n"
+ "fallback list (compiled in) is the source of truth.\n"
+ "\nResult:\n"
+ "{ active: 'T...', has_runtime_override: true|false }");
+
+ std::string active = Bootstrap::GetActiveTrustedSnapshotPublisher();
+ Object result;
+ result.push_back(Pair("active", active));
+ result.push_back(Pair("has_runtime_override", !active.empty()));
+ return result;
+}
+
+Value unsettrustedv2snapshotpublisher(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() != 0)
+ throw runtime_error(
+ "unsettrustedv2snapshotpublisher\n"
+ "Clear the runtime trusted snapshot publisher override.\n"
+ "The built-in fallback list (compiled in) becomes the source of truth again.\n"
+ "Removes /snapshot-publisher.json.\n"
+ "\nResult:\n"
+ "{ unset: true, fallback_in_effect: true }");
+
+ std::string err;
+ if (!Bootstrap::UnsetTrustedSnapshotPublisher(err)) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, err);
+ }
+ Object result;
+ result.push_back(Pair("unset", true));
+ result.push_back(Pair("fallback_in_effect", true));
+ return result;
+}
diff --git a/src/trianglesrpc.cpp b/src/trianglesrpc.cpp
index c7af877..a6d313f 100644
--- a/src/trianglesrpc.cpp
+++ b/src/trianglesrpc.cpp
@@ -317,7 +317,10 @@ static const CRPCCommand vRPCCommands[] =
{ "getcheckpoint", &getcheckpoint, true, false },
{ "gencheckpoints", &gencheckpoints, true, false },
{ "publishcheckpoint", &publishcheckpoint, true, false },
- { "getchaintips", &getchaintips, true, false },
+ { "settrustedv2snapshotpublisher", &settrustedv2snapshotpublisher, false, false },
+ { "gettrustedv2snapshotpublisher", &gettrustedv2snapshotpublisher, false, false },
+ { "unsettrustedv2snapshotpublisher", &unsettrustedv2snapshotpublisher, false, false },
+ { "getchaintips", &getchaintips, true, false },
{ "invalidateblock", &invalidateblock, false, false },
{ "reconsiderblock", &reconsiderblock, false, false },
{ "recalculatesupply", &recalculatesupply, false, false },
diff --git a/src/trianglesrpc.h b/src/trianglesrpc.h
index c057d41..8488adf 100644
--- a/src/trianglesrpc.h
+++ b/src/trianglesrpc.h
@@ -228,6 +228,9 @@ extern json_spirit::Value getblockbynumber(const json_spirit::Array& params, boo
extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value gencheckpoints(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value publishcheckpoint(const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value settrustedv2snapshotpublisher(const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value gettrustedv2snapshotpublisher(const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value unsettrustedv2snapshotpublisher(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);