Skip to content

SecureWeave consensus

SecureWeave is the consensus layer built into NDSR. Every validator runs it. It collects signed votes from validators, decides when a round is final, detects validators that vote two ways, and stores every vote so the decision can be checked and replayed later. Everything Necter treats as final goes through it.

Round Vote kind What validators agree on Used for
exec:0x… execution the receipt hash of a direct call (and the module state transition) POST /v1/execute, validator fallback, escalated tasks
task:0x… finality the finality record of a committee round (its correct receipt hash, gas and the miners who matched it) mining projects (Finality)
epoch:<project>:<epoch>:<attempt> epoch the per-project reward receipt v2 miner payouts (Rewards)
reward:… reward a v1 module reward receipt legacy module vaults

Execution votes can also carry module state transitions. When a quorum agrees on the same receipt and the same transition for a module, that is a state attestation: the proof validators use for state sync.

Committee miners vote too (task votes), but on the Hub, not through SecureWeave. SecureWeave is what validators use to sign off on the committee’s result.

{
"round_id": "exec:0x…",
"kind": "execution",
"receipt_hash": "0x…",
"node_id": "ndsr-…",
"public_key": "0x…",
"signature": "0x…",
"round_signature": "0x…",
"payout_address": "0x…",
"round": { "module_address", "function", "input_hash", "gas_limit", "nonce", "issued_at" },
"receipt": { "v", "module_address", "function", "input_hash", "output_hash", "events_hash", "gas_used", "success" },
"state": [ { "module_address", "pre_seq", "pre_root", "post_seq", "post_root" } ]
}

Each vote has two ed25519 signatures from the validator’s node key:

  • signature: the protocol signature over the result, with a domain per kind: necter-receipt-v1: + receipt hash (execution), necter-finality-v1: + finality hash, necter-reward-v2: + receipt hash (epoch), necter-reward-v1: + receipt hash (v1 reward).

  • round_signature: binds the vote to one round and one payout address:

    necter-vote-v1
    <kind>
    <round_id>
    <receipt_hash>
    <payout_address or empty>
    <state_hash or finality_hash> ← sixth line only when the vote carries state or a finality record

    A protocol signature alone is not a vote, so a receipt signed for one round cannot be replayed in another or redirected to another payout address.

Receivers check both signatures, that node_id matches the public key, that the key is a current validator, and recompute round_id from round and receipt_hash from receipt. Votes are self-authenticating: anyone can verify them, and nodes can relay each other’s votes.

With n current validators, a round is final on hash H when H has votes from ⌊2n/3⌋ + 1 distinct validators that did not equivocate. On the testnet n = 4, so the quorum is 3 and any one validator can be down or faulty.

  • Finalization is a pure function of the vote set. Given the same votes, every node, auditor or tool computes the same state (pending, finalized, or failed when no hash can reach quorum any more), whatever order the votes arrived in.
  • Two different hashes can never both reach quorum while fewer than a third of validators are faulty: any two quorums share at least one honest validator.
  • For finality votes, validators agree on the whole record (finality_hash), not just the receipt hash, so they also agree on which miners were right.

A validator that signs two different hashes in the same round is an equivocator. The two signed votes are an EquivocationProof that anyone can verify. From then on, none of that validator’s votes count in that round, and the proof is recorded (GET /consensus/evidence) for slashing. At most two votes per validator per round are stored, so equivocation cannot be used to flood a node.

The same rule applies to miners on the committee level: two task votes by one key in one round are equivocation and slash the whole bond (Slashing).

A vote that arrives after the round finalized is answered late, with whether it agrees with the final hash. It does not change the decision. In reward rounds an agreeing late vote is still credited.

sequenceDiagram
  participant H as Hub
  participant V1 as Validator 1
  participant V2 as Validator 2
  participant V3 as Validator 3
  participant V4 as Validator 4
  H->>V1: round (execute / task bundle / epoch proposal)
  H->>V2: round
  H->>V3: round
  H->>V4: round
  Note over V1,V4: each executes or verifies, signs a vote
  V1->>V2: POST /consensus/votes (node-signed)
  V1->>V3: POST /consensus/votes
  V2->>V1: POST /consensus/votes
  V3->>V1: POST /consensus/votes
  Note over V1,V3: 3 of 4 agree → finalized on every node,<br/>computed from the same vote set
  V4-->>V1: late vote → outcome "late"
  H->>V1: GET /consensus/rounds/{round_id}
  V1-->>H: tally, state, equivocators, dissenters
  • Every validator POSTs its vote to /consensus/votes on every other validator, node-signed, retrying with exponential backoff. The answer is {"outcome": "accepted" | "duplicate" | "equivocation" | "late", "state": …}, or 422 for an invalid vote.
  • GET /consensus/rounds/{round_id} on any validator returns its view: tally per hash, state, quorum, equivocators and dissenters. It is open to anyone.

Every accepted vote and its round context is written to the validator’s consensus.db and replayed on restart, so a restarted validator reaches exactly the same decisions. Votes are kept for 7 days (finality votes at least until their project epoch’s reward receipt is settled) and pruned hourly. The highest state attestation per module is kept with its votes as proof.

The set of keys SecureWeave counts comes from the Hub (GET /api/validators, refreshed every 60 s by default, cached in consensus.db): approved validators, sorted by public key, with quorum = ⌊2n/3⌋ + 1. Nodes can also be pinned to a static set with --validators. External validators are accepted only at mainnet; during the testnet the validator set is operated by the Necter team.

How it fits with committees, audits and slashing

Section titled “How it fits with committees, audits and slashing”
flowchart LR
  C[Committee of miners<br/>task votes] --> B[Hub bundles the votes]
  B --> V[Validators verify,<br/>audit if contested or drawn]
  V -- finality votes --> SW{{SecureWeave<br/>⌊2n/3⌋+1}}
  SW -- finalized record --> U[Units for matching miners]
  U --> E[Epoch receipt]
  E -- epoch votes --> SW2{{SecureWeave}}
  SW2 -- attested receipt --> PV[(Project vault)]
  V -. wrong votes + audited finality .-> S[Slashing evidence]
  • Committees decide who computes; SecureWeave decides what is final.
  • Audits make finality records trustworthy even if a whole committee lies; the finality quorum is what turns an audit into a proof.
  • Slashing evidence for invalid results is a miner’s wrong vote plus a SecureWeave finality quorum on an audited record. Validator equivocation proofs are recorded too.
  • Payouts need an epoch quorum: the Hub cannot pay anyone without validator signatures.