Skip to main content

Sentinel Service Layer Specification — v1

Syrius-Facing Public Verification Nodes for NoM

Status​

Exploratory specification, v1 (peer-reviewed revision of the original v0 spec). Defines a minimal path to activate Sentinels as useful public infrastructure without changing consensus rules for the MVP. This revision is grounded in a line-by-line read of the Syrius source (syrius/, SDK znn_sdk_dart v0.0.7); claims about Syrius carry file:line receipts. Claims about go-zenon are marked UNVERIFIED because that source was not available for this review.


0. Peer-review summary (what changed from v0 and why)​

#ChangeRationale
1Added a second operator model: "Sentinel hosted inside Syrius." v0 only described an externally-run znnd.Syrius already runs a full go-zenon node in-process (node_utils.dart:243) and serves WS RPC from it. Hosting the Sentinel from inside Syrius is the strongest architectural option and was missing. (§4)
2Called out the libznn configuration gap as a hard prerequisite.The embedded-node FFI is RunNode()/StopNode() with no arguments (embedded_node.dart:15-21); Syrius cannot set bind address/port/TLS. Public serving needs libznn/go-zenon work the v0 spec ignored. (§4.2, §11 Phase A)
3Added an always-on / lifecycle requirement.Syrius is an interactive GUI that holds a wake-lock only while open (node_utils.dart:238); a Sentinel must be 24/7. (§4.3)
4Added a Security & Threat Model section.v0 had none. Exposing a public RPC server from a wallet process, replay-proofing service records, liveness vs. intent, and IP↔owner privacy all need treatment. (§9)
5Split health criteria into gating vs. scoring.v0 made chain-ID and reachability additive; a wrong chain ID must be exclusionary, not a -20. (§7)
6Hardened the service-record format (canonical signed payload + timestamp + expiry + nonce; defined verification).v0's "signature over endpoint" is replayable and undated. (§5)
7Corrected the evidence basis. go-zenon claim tagged UNVERIFIED; video/breadcrumb downgraded to motivation; the "abandoned Peers wiring" clue removed as dead code; real in-process-node evidence added. (§2)
8Anchored every phase to real Syrius symbols and clarified the on-chain blocker (SentinelInfo has no endpoint field). (§11)

1. Thesis​

Sentinels should not be treated as passive staking positions. Zenon's greenpaper frames them as nodes that improve relay availability for light clients — a public RPC / data-service edge between clients and consensus:

Syrius / Light Clients / Browser Apps / dApps
↓
Sentinel Service Layer (public RPC + data/proof serving)
↓
NoM Full Nodes / Pillars (consensus)

Pillars remain the consensus backbone. Sentinels become the public edge layer.

Note on provenance. Zenon's whitepaper, lightpaper, and greenpaper conflict on the Sentinel's role (whitepaper: observer, not in consensus; lightpaper: sharding consensus participant; greenpaper: light-client relay). The whitepaper and lightpaper are core-team documents; the greenpaper's normative status is less clear. This spec adopts the greenpaper "service edge" reading because it is the only one buildable today without protocol changes — but it is a design choice, not settled protocol. See Purpose & Architecture and Sources.


2. Evidence Basis​

2.1 go-zenon — UNVERIFIED in this review​

Hypothesis: Sentinels receive emissions but perform no measurable service; the "uptime" check only verifies registration duration, not availability/RPC/sync/proof-serving. This is plausible but was not confirmed — go-zenon was not inspected. Treat as a claim to verify before relying on it. (Action: confirm in the go-zenon Sentinel/reward modules.)

2.2 Syrius — verified against source​

Syrius already provides most of the machinery, and more than v0 claimed:

  • Runs a full node in-process — libznn via FFI in an Isolate (lib/embedded_node/embedded_node.dart:50-84, started at lib/utils/node_utils.dart:243).
  • That node already serves WS RPC — Syrius connects to ws://127.0.0.1:35998 right after start (node_utils.dart:235, constants.dart:123-124).
  • Sentinel staking UI; node management with multiple tiers + switching + URL validation (node_management_screen.dart).
  • A discovery slot that is implemented but empty: loader + validator (main.dart:180-192), shuffle (node_management_screen.dart:43), assets/community-nodes.json = [].
  • Node-agnostic consumer: all calls go through one zenon!.wsClient (node_utils.dart:79-110).

Correction to prior reports: the SentinelsListBloc? parameter sometimes cited as a "half-finished Sentinel wiring" in the Peers widget is dead code — unused at peers.dart:51, copy-pasted into two unrelated tables, and not present in either table typedef. Do not cite it as evidence.

2.3 Video / "breadcrumb" material — motivation only​

The "Upgrade Preview" sequence (Sentinel → Syrius → Upgrade → Online) is community interpretation, not engineering evidence. It may motivate the work; it must not be cited as a technical basis. Useful framing: Sentinels should be upgraded from staking objects into online service nodes.


3. Design Goals & Non-Goals​

Goals

  • A first working Sentinel service layer with no consensus change for the MVP.
  • Make Sentinels useful first; defer protocol-level reward scoring.
  • Support both an externally-run node and a node hosted inside Syrius.

Non-Goals (MVP)

  • Trustless, on-chain endpoint discovery (requires a protocol change — §11 Phase C).
  • Changing Sentinel rewards/economics.
  • Making Syrius itself a light client that verifies all proofs (separate effort; see §10).

4. Sentinel Service Node​

A Sentinel Service Node is a node operated by (or on behalf of) a registered Sentinel owner that exposes a public, synced, read-capable NoM RPC endpoint. Two deployment models are supported.

4.1 Model A — External node (operator-run znnd)​

The operator runs a separate public go-zenon node and advertises it. Syrius is the consumer. Lowest protocol risk; standard ops.

4.2 Model B — Hosted inside Syrius (embedded node served publicly)​

Syrius's existing embedded libznn node is exposed as the public endpoint — "run a Sentinel from inside the wallet." This is architecturally attractive (the engine already runs) but has a hard prerequisite the v0 spec omitted:

Syrius cannot currently configure the embedded node. The FFI exposes only RunNode() and StopNode() with no arguments (embedded_node.dart:15-21,96), and Syrius writes no node config (only logs use the data dir, main.dart:56). The node runs with whatever config is baked into libznn; Syrius reaches it at 127.0.0.1:35998, and whether it also binds a public interface is not determinable without the libznn/go-zenon source.

Required work for Model B (no consensus change, but not Syrius-only):

  • Extend the libznn FFI to accept config (bind address, port, enable public RPC, TLS), or have Syrius write a go-zenon config.json that the embedded node reads on RunNode().
  • Default to localhost-only unless the user explicitly opts into public serving.

4.3 Minimum requirements (both models)​

  • registered Sentinel owner address
  • running synced full node (fresh frontier momentum)
  • public IP or domain, reachable
  • wss with TLS for any non-localhost endpoint (plain ws:// only for localhost/trusted LAN)
  • correct chain ID
  • healthy peer count
  • ledger RPC + subscription support
  • always-on: for Model B, a headless / stay-alive / run-in-tray mode (a closed GUI must not silently drop the Sentinel)

Default port: 35998 (matches kDefaultPort). Example endpoints:

wss://sentinel.example.com:35998 # public (TLS required)
ws://127.0.0.1:35998 # local only

5. Sentinel Service Record (off-chain, MVP)​

Because the chain stores no endpoint (see §11 Phase C), the MVP binds an endpoint to a Sentinel owner with a signed off-chain record.

{
"owner": "z1...",
"endpoint": "wss://sentinel.example.com:35998",
"capabilities": ["ledger", "stats", "subscriptions", "embedded.read"],
"version": "0.1",
"issuedAt": 1717000000,
"expiresAt": 1719592000,
"nonce": "base64-random",
"publicKey": "owner-public-key",
"signature": "sig over canonical(payload-without-signature)"
}
  • Required: owner, endpoint, capabilities, version, issuedAt, expiresAt, nonce, signature (and publicKey if the owner address is not sufficient to recover/verify it).
  • Optional: nodeId, region, operatorName.
  • Signature is over a canonical serialization of all fields except signature — not just the endpoint — to prevent field tampering and replay. Verifiers MUST:
    1. check expiresAt is in the future and issuedAt is sane;
    2. verify the signature against the owner's key and confirm it matches owner;
    3. cross-check that owner is in the on-chain active set (embedded.sentinel.getAllActive() / getByOwner()), so a record can't advertise a revoked or non-existent Sentinel;
    4. then probe (§7). A valid signature proves intent, never liveness.

Records should be short-lived (rotate before expiresAt) so a stale endpoint can't be replayed indefinitely.


6. Syrius Discovery Flow​

Add a node category "Sentinel Nodes" in Node Management. Flow:

  1. Load Sentinel service records (signed JSON; initially a curated list, later a feed).
  2. Validate record (signature, expiry, owner ∈ active set) — §5.
  3. Validate endpoint format (InputValidators.node).
  4. Probe: open WS, check chain ID, sync freshness, peer count, RPC correctness — §7.
  5. Score and rank healthy nodes — §8.
  6. Cache results; re-probe periodically; demote nodes that go stale/unreachable.
  7. Display in Node Management; allow connect.
  8. On active-node failure, fail over to the next-best ranked node (Syrius holds a single zenon!.wsClient today — this needs adding).

Bootstraps by reusing the existing community-node path: populate community-nodes.json (or a Sentinel-specific equivalent) instead of leaving it [].


7. Health Checks — gating vs. scoring​

Split criteria. Gating failures exclude a node (never connect); scoring ranks the survivors.

Gating (pass/fail — failure = exclude):

  • Reachability — WS connection opens.
  • Chain ID — matches expected (a wrong chain ID is disqualifying, not a deduction).
  • Sync validity — node returns a valid, non-bogus frontier.

Scoring (rank the gated-in nodes):

SignalWhat it measuresSource
Sync freshnessfrontier height vs. best-knownledger.getFrontierMomentum
RPC correctnessanswers core calls correctlystats.syncInfo, stats.networkInfo
Subscription supportlive updates worksubscribe.toMomentums, subscribe.toAllAccountBlocks
Peer healthpeer_count >= thresholdstats.networkInfo
Latencyround-trip on probe callsmeasured during probe

Sample over a short window rather than a single shot, and decay scores so a node that degrades drops in ranking.


8. MVP Scoring (local, off-chain)​

SentinelScore = sync_freshness + rpc_correctness + subscription + peer_health + latency
# only computed for nodes that PASS all gating checks (§7)

Suggested weights (gated-in nodes only):

ComponentWeight
Sync freshness30
RPC correctness25
Latency20
Subscription support15
Peer health10
Total100

Status bands:

ScoreStatus
85–100Excellent
65–84Healthy
40–64Degraded
0–39Do not use

Reachability and chain ID are intentionally absent from the additive score — they are gates in §7. A node that fails them is excluded, not low-scored.

Mnemonic. These dimensions map onto the community "Upgrade Preview" mnemonic — SIZE (history served), SPEED (latency/sync), POWER (capacity/uptime), BUMP (peer propagation/relay), SLIDE (failover/routing). It's a handy memory aid for operators; the authoritative thresholds and weights are this section and §7. See Upgrade Preview video.


9. Security & Threat Model (new in v1)​

  • Don't co-locate a hot wallet with a public server unguarded. Model B exposes an RPC server from the same application a user holds keys in. Public RPC MUST be read-scoped (no key material, no signing, no wallet endpoints), and ideally isolated from the wallet context. Default off; explicit opt-in; clear warning.
  • DoS / resource exhaustion. A public endpoint invites abuse. Recommend rate limiting, connection caps, and (later) the whitepaper's PoW-link / Plasma-style cost on requests.
  • Transport security. TLS (wss) mandatory for non-localhost; reject mixed/plain public endpoints in discovery.
  • Liveness ≠ intent. A signed record proves the owner advertised an endpoint, not that it is up, synced, honest, or still a registered Sentinel — hence mandatory active-set cross-check (§5) + live probing (§7).
  • Endpoint spoofing / replay. Canonical signed payload + expiresAt + nonce (§5).
  • Privacy. Advertising an endpoint links a Sentinel owner address ↔ IP/domain. Operators should understand this; consider domain fronting / reverse proxies. Some owners may prefer Model A behind infrastructure they control.
  • Trust minimization. Clients should treat Sentinels as transport, not authority (§10).

10. Relationship to Verification-First Design​

Verification-first mindset: don't trust the server — request data, verify locally. Sentinels should be proof-serving transport, not trusted RPC authorities. Long-term capabilities to serve:

  • momentums, account blocks
  • state proofs, frontier proofs
  • historical data
  • (if SPV is added) Bitcoin headers/proofs

The client verifies; the Sentinel transmits — matching the whitepaper's storage/transmission role. (Full client-side verification in Syrius is out of MVP scope; this section sets the direction so the RPC surface doesn't bake in trust assumptions.)


11. Build Phases​

Phase 0 — Documentation & verification. Write up the gap; verify the go-zenon reward claim (§2.1). No code.

Phase A — Make Syrius serve as a Sentinel (the load-bearing new work). No consensus change.

  • Extend libznn FFI or add a Syrius-written go-zenon config.json so the embedded node can bind publicly + enable wss/TLS (§4.2).
  • Add a "Run as Sentinel / serve publicly" mode with an always-on lifecycle + sync/reachability status (§4.3). (Model A operators can skip this and run znnd directly.)

Phase B — Discovery & quality (per §5–§8). No consensus change.

  • Signed service records (§5).
  • Populate the discovery slot; reuse _loadDefaultCommunityNodes (main.dart:180), InputValidators.node, and the shuffle.
  • Health probing/scoring reusing establishConnectionToNode (node_utils.dart:20) + getNodeChainIdentifier (node_utils.dart:28) + frontier freshness + peer count.
  • RPC failover across ranked nodes (Syrius currently holds one wsClient).

Phase C — Trustless on-chain (requires spork/governance).

  • The blocker: on-chain SentinelInfo has only owner, registrationTimestamp, isRevocable, revokeCooldown, active; register() encodes ('Register', []) with no endpoint (SDK model/embedded/sentinel.dart, api/embedded/sentinel.dart:51).
  • Add an endpoint (and optional capability/service-key) field to Sentinel registration + SentinelInfo + SDK, so getAllActive() yields discoverable endpoints.
  • Then move reward weighting from registration-duration to service quality:
    reward_weight = stake_valid × reachability × sync_freshness × service_correctness × uptime

Operator tooling (parallel, via znn-controller): register/test/sign/publish service record, monitor health. (Syrius already hands operational Sentinel management to znn-controller today — sentinel_stepper_container.dart:678-699.)


12. Minimal Implementation Target​

The smallest genuinely-useful release:

  1. A Sentinel operator exposes a public, synced endpoint — via Model A (run znnd) today, or Model B (Syrius serve-mode) once Phase A lands.
  2. The endpoint is published as a signed record.
  3. Syrius validates (signature + active-set), probes, scores, and ranks it.
  4. Users connect, with failover.

No consensus change required.


13. Summary​

  • Pillars = consensus.
  • Sentinels = public verification/service edge.
  • Syrius = client that consumes that edge and can host it (Model B).

The missing algorithm is not exotic: discover → verify (signature + active-set) → probe → rank → connect (with failover) → eventually reward by service. The MVP is off-chain and needs no spork. The two honestly-underrated pieces of real work are (1) making Syrius's embedded node serve publicly (libznn/go-zenon, not just UI) and (2) keeping it alive 24/7. Everything else sits on infrastructure that already ships in Syrius.


Reviewer's open questions (carry into Phase 0)​

  1. go-zenon RPC config: does libznn's baked-in config already expose a public-capable RPC server, or is binding 0.0.0.0/TLS entirely new code? This decides how big Phase A is. (Not answerable from syrius/; needs go-zenon.)
  2. go-zenon reward logic: is "uptime = registration duration" actually true? (§2.1)
  3. Signature scheme: can a Sentinel owner address verify a record signature directly, or must publicKey be carried? (Depends on Zenon address/sig recovery semantics.)
  4. Protocol appetite: is there governance willingness for the Phase C endpoint field, or should the off-chain record be treated as the long-term mechanism?