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);