Expand description
One CapacityBond watcher feeding five registry projections.
ChainStakerSet (membership: NodeId → active?), ChainNodeAddressDirectory
(bindings: NodeId → operator address), the region map (NodeId → regionHint),
the operator reverse map (operator address → NodeId), and the
DialAddrDirectory (NodeId → registry multiaddrs, as iroh direct-dial
addresses) are all derived from the same CapacityBond contract. This module
runs one enumeration and one eth_getLogs loop over the shared
CapacityBond address, demuxing to all five projections; node-address’s two
topics are a strict subset of the route’s eight. RegionUpdated feeds the
region map alone, and NodeMultiaddrUpdated the dial-address directory alone.
Bindings is gated on cache.node_to_node_pull_through_enabled: when
pull-through is off, the bindings projection is not built at all. Regions,
the operator reverse map, and the dial-address directory are always built,
regardless of pull-through — the ADR-030 selection penalty needs the region
map unconditionally, the chain-backed origin directory needs the reverse map
to resolve operators locally with no nodeIdOf RPC, and every node dial
(DHT included) resolves direct addresses through the dial-address directory.
§What actually differs between the projections
Mostly the enumeration. The live event arms are close to a clean union:
NodeRegistered inserts into active, bindings (if built), regions (if
non-empty), the reverse map, and the dial-address directory (if any address
decodes), all unfiltered — staker-set’s live arm
applies no isActive check either (its filter is bootstrap-only). It is
tempting to read “staker-set is the filtered view, the rest are the
unfiltered ones” as a live-path difference and encode it in the sink; that
would be wrong, and would drop registrations from the active set.
At bootstrap active and bindings genuinely diverge:
activetakes the per-entryactive[i]thatgetRegisteredNodescomputes on-chain (isActive(operator): registered AND bond ≥ minBond AND no unbonding AND not ejected) alongside the_registeredAddrspage.bindingsapplies no filter: an operator mid-unbonding hasisActive = falsebut is still payable, so its binding must survive.regions, the reverse map, and the dial-address directory are likewise unfiltered: liveness is applied separately at read time via the sharedStakerSet, and an inactive node stays dialable for channel settlement.
§Fatality
The single getRegisteredNodes bootstrap is fatal: staker-set requires it.
A transient RPC failure retries on the shared boot budget; a deterministic
fault or an exhausted budget propagates. The bindings projection (pull-through is
opportunistic, so it would otherwise be non-fatal) has no RPC of its own:
it is derived from page data already in hand and cannot fail independently.
The same is true of regions, the reverse map, and the dial-address directory.
A node either boots with all five projections or fails at the one shared read, so pull-through is
never lost on its own.
Structs§
- Registry
Handles - Both projections, plus the shared watcher that keeps them current.
Functions§
- bootstrap
- Enumerate
CapacityBondonce, then spawn the single watcher that keeps both projections current.