Expand description
Lazy, bounded, TTL-split cache of getOrigins(namespaceId) results.
ChainOriginDirectory resolves a namespace to its authorized origin
operators lazily, on first request, rather than by watching every
getOrigins event up front. Namespace creation is permissionless and
free, so an attacker can mint namespace IDs at will; if every request for
an unknown namespace forced a fresh getOrigins RPC, a burst of
attacker-chosen namespaces would turn into a proportional burst of chain
calls. This cache exists to break that link: it remembers a namespace’s
result — including “this namespace has no origins” — for a bounded time,
so repeated lookups against the same (possibly bogus) namespace cost one
RPC, not one per request.
A non-empty getOrigins result is a POSITIVE entry and lives for
positive_ttl. An empty result (no origins, including a namespace that
does not exist on chain) is a NEGATIVE entry and lives for the shorter
negative_ttl — the actual DoS bound: it caps how often an attacker can
force a real RPC per bogus namespace, while keeping genuinely-empty
namespaces from squatting on cache space or masking a legitimate
getOrigins call for very long once one is registered.
The cache stores the raw operator ADDRESS set, never resolved NodeIds.
Resolving addresses to live NodeIds (via the capacity-bond registry’s
reverse projection) and filtering by StakerSet liveness both happen at
READ time, in resolve_active — so a TTL-anchored cache entry never goes
stale on the parts of the answer that change without a new getOrigins
call: operator staking status and operator→node bindings.
Modeled directly on super::probe_cache: an IndexMap doubles as the
store and the LRU order (index 0 = most-recently-used), the TTL is
anchored on Instant at insert and never refreshed on read, and the lock
is poison-tolerant.