Skip to content

Committees and sortition

For every task of a mining project, a committee of miners executes the same call and votes on the receipt hash. Anyone can recompute who was on a committee from public data.

A project has fixed-length epochs (epoch_secs = 3600 × m with m ∈ 24, aligned to UTC; immutable for a project). Epoch E starts at E × epoch_secs. Every task takes the next slot (project_id, E, seq), with seq counting from 0 in arrival order. The committee of a slot is fixed before the task content exists.

T(E) = start(E) − beacon_offset_secs (960 s on the testnet)
B(E) = the last Sepolia block with timestamp < T(E)
beacon(E) = keccak256("necter-beacon-v1" ‖ blockhash(B(E)))

Before each epoch (at least snapshot_lead_secs = 300 s ahead), the Hub builds a snapshot of the project’s eligible subscriptions as of block B(E): bonded, not unbonding, not jailed, collateral ≥ the project’s min_collateral, bound to the same owner as the subscription, not paused. Each entry has the node key, owner, payout address, collateral, reputation and binding hash.

Every validator checks the snapshot independently against its own Sepolia RPC (replaying Staking events and verifying bindings) and signs it. A snapshot is valid only with signatures from floor(2n/3)+1 validators. Without a valid snapshot the epoch’s tasks use validator fallback.

Terminal window
curl -s https://testnet-rpc.necter.network/v1/committees/<project_id>/<epoch> # attested snapshot
curl -s https://testnet-rpc.necter.network/v1/committees/<project_id>/<epoch>/<seq> # one slot's draw
cap_i = min(collateral_i, weight_cap) weight_cap = min_collateral × weight_cap_multiple
w_i = floor(cap_i / 10^15) × reputation_i (reputation in basis points, 1000..10000)

More collateral and a better reputation raise a miner’s chance of selection, up to the cap, which limits how much one large bond can dominate.

seed(slot) = keccak256("necter-sortition-v1" ‖ beacon(E) ‖ project_id ‖ u64be(E) ‖ u64be(seq) ‖ set_hash)
k_eff = min(size, N); b_eff = min(backups, N − k_eff)
for j in 0 .. k_eff + b_eff − 1:
x = uint256(keccak256(seed ‖ u32be(j))) mod W # weighted draw without replacement
pick the entry whose cumulative weight first exceeds x; remove it

The first k_eff picks are the primary members; the rest are ordered backups. If fewer than min_size miners are eligible, the slot has no committee and runs as a validator round.

sequenceDiagram
  participant H as Hub relay
  participant P as Primary member
  participant B as Backup
  H->>P: offer (position, accept_by = now + 10 s)
  alt accepts in time
    P->>H: accept (node-signed)
    H->>P: lease (input, deadline)
    P->>H: result + vote
  else declines, times out, offline, or misses the lease deadline
    H->>B: offer for the same position
  end
  H-->>P: outcome (finalized / escalated / failed, your_vote, units)

Declining an offer is never penalized. Accepting and then missing the lease costs reputation (collateral slashing for missed leases is coming soon). The committee quorum is floor(2·k_eff/3)+1 over primaries and activated backups. A round is clean when that many votes agree and none disagrees; otherwise it is contested, and validators always audit contested rounds. Validators then finalize the round through SecureWeave. Finality

Each subscription starts at 5,000 bp and changes when an epoch closes:

rep(E+1) = clamp(rep(E) + 25·min(a,40) − 150·m − 25·min(o,40) − 2500·i, 1000, 10000)

a = accepted leases whose vote made it into a finalized round, m = accepted leases with no valid vote in time, o = offers left unanswered, i = votes proven invalid. The snapshot for epoch E uses the value computed when epoch E−2 closed.