Skip to content

Milestone 3 · Network

Nuvex maintainers
Network racks with fibre running across black doors

The predicate, check by check

Selection has to be a function of public inputs, because a private backend that picks a fulfiller cannot be audited from the chain. In Milestone 3 that function is an eligibility test inside fulfill, and every condition is read from accounts.

  • The requester did not name the fulfiller, and the node authority signs.
  • The node account was created at a slot strictly earlier than the request.
  • The VRF public key was set at registration, is not all zeros, and update_node cannot replace it.
  • The capability mask includes the VRF bit, and the proof verifies under the stored key.
  • Node status is Active, and stake is at or above min_stake.
  • last_heartbeat is non-zero and the current slot is inside last_heartbeat plus heartbeat_timeout_slots.
  • The registry's unstake_cooldown_slots and heartbeat_timeout_slots are both non-zero.
Node heartbeat···
Illustration · not chain data
Illustration · not chain data

First come, not weighted

There is no assignment step and no draw. Among the nodes that satisfy the predicate, the first transaction to land wins, and a second fulfilment of the same request is rejected.

“VRF fulfillment is first-come among nodes that meet the ADR 0003 predicate. There is no weighted lottery.”
Protocol README, Open decisions

Reputation exists as a counter, not as a weight. It increases by one through note_fulfillment, which only the oracle-core protocol PDA may call, and a slash sets it to zero.

What fulfilment records

The values the predicate checked are written onto the request as assigned_node, assigned_stake and assigned_heartbeat. They are the numbers at the moment of the check rather than a balance someone can read later, which is what makes the decision recomputable by an outside observer.

Open requestOPEN
job_type
VRF
requester
your program
input
32-byte seed
callback
consumer program
max_fee
recorded, not charged
Illustration · not chain data
Illustration · not chain data

Three statuses and one cooldown

  • Registered: the stake may sit below the minimum, and the node can neither fulfil nor heartbeat.
  • Active: a deposit reached min_stake.
  • Unstaking: the whole stake stays locked until the cooldown ends, and heartbeat and fulfil are rejected.

Withdraw returns the accounted stake after the cooldown and sets the node back to Registered. A node cannot deposit while unstaking. The cooldown and the heartbeat window are registry fields that must each sit between 1 and 150,000 slots, which is the existing request-timeout safety bound rather than a chosen economic duration. Slash takes an amount named by the slash authority, during the cooldown if need be, and there is no automatic percentage.

Nothing in this tree submits a fulfilment

The predicate is implemented on-chain, and the off-chain half that would race for it is not. The node process runs a health and metrics server on /health, /live, /ready and /metrics. It does not load signing keys, connect to an RPC or fulfil jobs.

“A node process in this repository still does not submit transactions.”
ADR 0003: Node selection, Consequences

The same holds one layer up: the Rust SDK can prove a VRF output on the host, the TypeScript SDK's submitRequest throws on purpose, and the CLI reports the milestone each command is waiting on. The threat model counts that as the current mitigation for a node that trusts a lying RPC, which is an accurate description of a gap, not a feature.

nuvex-oracle-nodeHealth only
  • /healthok
  • /metricsexposed
  • VRF keynot loaded
  • fulfilmentsnot submitted
Illustration · not chain data

The part a predicate cannot fix

Eligibility says who may submit. It does not say whose output gets chosen when one operator controls several eligible keys, because every proof under every one of those keys is valid.

“An operator who funds several keys can still submit the output they prefer.”
ADR 0003: Node selection

The cost of another identity is another deposit of at least min_stake, the registry authority sets that number, and zero is allowed. Stake-weighted assignment would need an on-chain node index that does not exist and a weight that nobody has justified, so the limitation is published rather than closed.

A night sky over a mountain ridge
Nuvex maintainersMilestone 5

Protocol

A price median is not a proof

Milestone 5 asks public venues for SOL, BTC, and ETH, drops anything stale or failed, and returns the median only when enough sources remain. The programs still reject a Price request.

Read the post
Earth from orbit beside a spacecraft
Nuvex maintainersMilestone 4

Protocol

The read model copies accounts. It does not decide them

Milestone 4 adds an indexer, a PostgreSQL store, and a read API the console and CLI can query. An empty list is a real answer. A row in that list is still not a finalized result.

Read the post