feat: invalidate OPRF node RP signer cache on RpUpdated events#821
Draft
Takaros999 wants to merge 1 commit into
Draft
feat: invalidate OPRF node RP signer cache on RpUpdated events#821Takaros999 wants to merge 1 commit into
Takaros999 wants to merge 1 commit into
Conversation
The RpRegistryWatcher previously cached an RP's signer purely by TTL
(default 10m) with no event-driven invalidation, diverging from WIP-101
§8 ("OPRF Nodes MUST invalidate the signer cache on any RpUpdated
event"). For EOA signers this means a rotation or deactivation — e.g.
revoking a compromised signer — only takes effect once the cached entry
expires, leaving up to a full TTL window in which the stale signer is
still accepted.
Add a background loop that polls the RpRegistry for RpUpdated logs over
HTTP (eth_getLogs) and invalidates the cached entry of each updated RP,
collapsing the propagation delay to roughly one poll interval. The TTL
remains the backstop if the loop stalls.
Reliability:
- Fail-safe: RPC errors are logged and the same block range is retried
on the next tick; the loop only exits on cancellation, never failing
the request path.
- Over-invalidation is safe (worst case is one getRp refetch), so reorgs
are ignored rather than tracked.
- Observability: adds an invalidations counter and a last_polled_block
heartbeat gauge so a stalled poller can be alerted on.
Config: new rp_registry_poll_interval (default 2s).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
|
We discussed this during implementing wip101 and agreed that the TTL of 10 minutes if fine and we don't need an instant cache eviction on such cases. I will reject this except there is a really (really) strong reason why this might be a problem (which I don't see and was already deemed ok 1 month ago). Edit: happy do discuss smaller TTLs |
Collaborator
|
Will close this at end of the week if there is no activity |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
RpRegistryWatcherin the OPRF node caches each RP's signer purely by TTL (default 10m) with no event-driven invalidation, diverging from WIP-101 §8 — "OPRF Nodes MUST invalidate thesignercache on anyRpUpdatedevent emission from theRpRegistry."For EOA signers (the common case), rotation is an atomic swap of the single registered
signeraddress inRpRegistry. With TTL-only caching, a rotation — including revoking a compromised signer viaupdateRp(rotate) ortoggleActive=false(kill switch) — only takes effect once the cached entry expires. That leaves up to a full TTL window where the stale/stolen signer is still accepted, and during a planned rotation a window where neither key is accepted fleet-wide.This PR adds a background loop that polls
RpRegistryforRpUpdatedlogs over HTTP (eth_getLogs) and invalidates the cached entry of each updated RP, collapsing the propagation delay from the TTL to roughly one poll interval (default 2s). The TTL stays as the backstop.Changes
rp_registry_watcher.rs:run_invalidation_loop()— pollsRpUpdated, callsrp_store.invalidate(rpId). Cancellation-aware, fail-safe.lib.rs: spawn the loop at startup, wired to the existing cancellation token.config.rs: newrp_registry_poll_interval(default 2s).metrics.rs:…cache.invalidationscounter +…cache.last_polled_blockheartbeat gauge.Reliability notes
getRp), so reorgs are ignored rather than precisely tracked.last_polled_blocklets us alert when the poller stops advancing (a stalled poller silently degrades back to TTL-only — security-relevant).Test plan
cargo check/cargo clippy(crate deniesclippy::all+pedantic+unwrap_used) cleantest_event_driven_invalidation(anvil): prime cache → on-chain signer rotation → loop evicts entry → refetch returns rotated signerrp_registry_watchertests passFollow-ups (not in this PR)
last_polled_blocknot advancing.🤖 Generated with Claude Code