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>
This commit is contained in:
@@ -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=<your-onion-address>` in `triangles.conf`.
|
||||
- `smsglocalkeys` - List messaging-enabled addresses
|
||||
- `smsgscanchain` - Scan blockchain for public keys
|
||||
|
||||
### Trusted Snapshot Publisher (v6.1.8+)
|
||||
- `settrustedv2snapshotpublisher <address>` - Atomically replace the trusted snapshot publisher (previous one dropped immediately). Persists to `<datadir>/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
|
||||
|
||||
@@ -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` | `<address>` | Atomically replaces the trusted snapshot publisher. The previous publisher is dropped immediately (no grace period). The new publisher is persisted to `<datadir>/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 `<datadir>/snapshot-publisher.json`. |
|
||||
| `invalidateblock` | `<hash>` | Permanently marks a block as invalid and rewinds the chain past it. |
|
||||
| `reconsiderblock` | `<hash>` | 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. |
|
||||
|
||||
@@ -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 <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:
|
||||
```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 `<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
|
||||
|
||||
```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 <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.
|
||||
+118
-6
@@ -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<std::mutex> lock(g_trustedPublisherMutex);
|
||||
return g_activeTrustedSnapshotPublisher;
|
||||
}
|
||||
|
||||
static void SetActiveTrustedSnapshotPublisherUnlocked(const std::string& addr)
|
||||
{
|
||||
g_activeTrustedSnapshotPublisher = addr;
|
||||
}
|
||||
|
||||
// Load runtime override from <datadir>/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":"<addr>"
|
||||
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// <datadir>/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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "utxosnapshot.h"
|
||||
#include "checkpointpublisher.h"
|
||||
#include "wallet.h"
|
||||
#include "bootstrap.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
@@ -1322,3 +1323,91 @@ Value dumputxoset(const Array& params, bool fHelp)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Trusted snapshot publisher RPCs (Design A: single-slot rotation)
|
||||
// ============================================================================
|
||||
//
|
||||
// settrustedv2snapshotpublisher <address>
|
||||
// - Atomically replaces the active trusted snapshot publisher.
|
||||
// - The previous publisher is dropped immediately (no grace period).
|
||||
// - The new publisher is persisted to <datadir>/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 <datadir>/snapshot-publisher.json.
|
||||
Value settrustedv2snapshotpublisher(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"settrustedv2snapshotpublisher <address>\n"
|
||||
"Atomically replace the trusted snapshot publisher.\n"
|
||||
"The previous publisher is dropped immediately (no grace period).\n"
|
||||
"The new publisher is persisted to <datadir>/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 <datadir>/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;
|
||||
}
|
||||
|
||||
@@ -317,6 +317,9 @@ static const CRPCCommand vRPCCommands[] =
|
||||
{ "getcheckpoint", &getcheckpoint, true, false },
|
||||
{ "gencheckpoints", &gencheckpoints, true, false },
|
||||
{ "publishcheckpoint", &publishcheckpoint, 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 },
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user