Expand description
Node-id → operator Ethereum address resolver (ADR 003 §Node Registry, #831).
The node-to-node cache-miss pull path discovers upstream providers by iroh
NodeId (DHT FIND_VALUE / origin directory), but paying one requires
its bonded operator address: crate::buyer_channel::BuyerPoolService
opens the USDC pool to that address, and the pull’s open stage
(open_progressive_pull) verifies the delivery slash_sig
recovers to it. This module resolves that binding from the same
CapacityBond data crate::dht::chain_staker_set::ChainStakerSet already
reads — getRegisteredNodes() returns NodeInfo { nodeId, ethAddress, .. } and
NodeRegistered(nodeId, ethAddress, ..) carries both — so no new contract
surface is needed.
This module owns the projection only. Because both views come from one
contract, crate::dht::capacity_bond_registry does a single
getRegisteredNodes enumeration and runs a single eth_getLogs loop feeding
both, building this via ChainNodeAddressDirectory::from_parts (#1110).
The binding is set at registerNode and cleared at deregisterNode; it is
unaffected by bond / unbonding / ejection transitions (those flip
isActive, which the staker set tracks separately). So the registry’s
bindings arms follow only NodeRegistered (insert) and NodeDeregistered
(remove) — notably NodeAutoEjected deactivates without clearing a binding —
and the enumeration applies no isActive filter: an operator mid-unbonding is
momentarily inactive but still payable.
§Failure model
Bootstrap: the bindings projection is derived from page data the
(unconditional; fatal on a deterministic fault or once its boot retries are
exhausted) staker-set
enumeration already read, so it has no RPC of its
own and cannot fail independently — see capacity_bond_registry’s
§Fatality. It is simply not built when
cache.node_to_node_pull_through_enabled is off.
Watcher RPC failure mid-run → the shared loop logs at warn!, backs off
(1s → 60s cap), and re-polls. The cursor is retained across the backoff, so
the next eth_getLogs tick re-scans [cursor, head] and re-applies any
binding event that landed during the outage — no backoff-gap drift. A missing
binding fails the pull closed — the orchestrator skips a provider it cannot
resolve rather than guessing an address it would then pay.
Structs§
- Chain
Node Address Directory - Chain-backed
NodeAddressResolver. Cheap to clone via the shared innerArc; the background loop that keeps the cache fresh is the single shared multiplexed-poller task the runtime owns, so a node-restart cycle never leaks chain-poll tasks. - Static
Node Address Directory - Static, in-memory
NodeAddressResolverfrom a known map. Used by tests and any caller wiring a fixed binding set without a live chain.
Traits§
- Node
Address Resolver - Read-only resolver from a provider’s iroh
NodeIdto its bonded operator EthereumAddress. Implementations MUST be cheap to clone (typicallyArc<inner>); the pull orchestrator holds a long-livedArc<dyn NodeAddressResolver>and callsSelf::address_ofonce per candidate it intends to pay.