Skip to content

[3/n][guardian-integration] add local limiter (synced with guardian) to hashi nodes#484

Merged
0xsiddharthks merged 8 commits intomainfrom
siddharth/guardian-local-limiter
Apr 28, 2026
Merged

[3/n][guardian-integration] add local limiter (synced with guardian) to hashi nodes#484
0xsiddharthks merged 8 commits intomainfrom
siddharth/guardian-local-limiter

Conversation

@0xsiddharthks
Copy link
Copy Markdown
Contributor

@0xsiddharthks 0xsiddharthks commented Apr 23, 2026

Adds an local emulator of the guardian's token bucket (and sequence).

  • Hashi:
    • Adds LocalLimiter (crates/hashi/src/guardian_limiter.rs)
    • Bootstrap the local limiter during hashi startup
  • Guardian:
    • GetGuardianInfoResponse extended with LimiterState + LimiterConfig

We are not currently updating the local limiter when processing new transactions. that is being done in #495

@0xsiddharthks 0xsiddharthks requested a review from bmwill as a code owner April 23, 2026 14:06
@0xsiddharthks 0xsiddharthks changed the title [guardian] add local limiter emulator with bootstrap + reconciliation [3/n][guardian-integration] add local limiter emulator with bootstrap + reconciliation Apr 23, 2026
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-e2e-harness branch from 56510fb to 1bb33df Compare April 26, 2026 21:22
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-local-limiter branch from 178b639 to b3a5c1b Compare April 26, 2026 21:22
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-e2e-harness branch from 1bb33df to f4fbdb6 Compare April 26, 2026 22:03
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-local-limiter branch from b3a5c1b to f2e3300 Compare April 26, 2026 22:03
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-e2e-harness branch 3 times, most recently from 96f3774 to 49100e6 Compare April 27, 2026 03:27
@0xsiddharthks 0xsiddharthks marked this pull request as draft April 27, 2026 12:10
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-local-limiter branch from f2e3300 to f826f4b Compare April 27, 2026 21:34
@0xsiddharthks 0xsiddharthks changed the title [3/n][guardian-integration] add local limiter emulator with bootstrap + reconciliation [3/n][guardian-integration] sync guardian limit to hashi nodes Apr 28, 2026
@0xsiddharthks 0xsiddharthks changed the title [3/n][guardian-integration] sync guardian limit to hashi nodes [3/n][guardian-integration] add local limiter (synced with guardian) to hashi nodes Apr 28, 2026
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-local-limiter branch from f826f4b to 7ef840d Compare April 28, 2026 14:26
@0xsiddharthks 0xsiddharthks marked this pull request as ready for review April 28, 2026 15:02
optional uint64 next_seq = 3;
}

// Immutable limiter configuration.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't exactly immutable since the guardian can opt to change it

@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-e2e-harness branch from 67c9469 to dffff49 Compare April 28, 2026 21:44
Base automatically changed from siddharth/guardian-e2e-harness to main April 28, 2026 21:57
Adds a `LocalLimiter` that mirrors the guardian's token-bucket state so
each hashi node can project capacity and pick the next `seq` without a
round-trip. Observational in this PR — no withdrawal-flow change yet;
the rewiring lands in the next PR.

- `crates/hashi/src/guardian_limiter.rs` — `LocalLimiter` with
  `validate_consume` / `apply_consume` / `capacity_at` / `snap_to`
  under a `tokio::Mutex`, plus 8 unit tests.
- `Hashi::start` — fetch `GetGuardianInfo` once at startup, cache the
  Ed25519 signing pubkey, seed the `LocalLimiter`. Best-effort; the
  node still starts when the guardian is unreachable.
- `start_limiter_reconcile_service` — 30 s background task. Covers
  late bootstrap, non-leader drift, and leader rotation. Snap only
  when the guardian is strictly ahead (`next_seq` or `last_updated_at`
  strictly greater) to avoid moving local state backwards on a stale
  guardian snapshot. Also refreshes `guardian_signing_pubkey` if
  bootstrap missed it.
- `GetGuardianInfoResponse` extended with `LimiterState` +
  `LimiterConfig` so one RPC seeds the full emulator.
In the simplest case the 30 s reconciliation loop has nothing to do —
the leader stays in sync via `apply_consume` after every successful
guardian RPC, the next PR's error-path snaps from the guardian
unconditionally on any RPC rejection, and the wasted-MPC-round window
at leader promotion is better covered by an on-demand snap at the
false→true leader edge (landing alongside the withdrawal wiring).

Keep the bootstrap half of the sync story; replace the recurring
reconcile with a short-lived retry.

- `try_seed_guardian_state` — idempotent helper that writes each
  field at most once (`OnceLock::set` for the pubkey, `is_none()`
  guard on the `local_limiter` slot). Called synchronously in
  `Hashi::start` so the happy path is seeded before any other
  service starts.
- `start_guardian_bootstrap_retry` — short-lived task that fast-exits
  when no guardian is configured or state is already seeded, else
  retries with bounded exponential backoff (1 s → 30 s) until the
  first success, then returns.
- Delete `start_limiter_reconcile_service` + `install_local_limiter`.
  The reconcile's "strictly ahead" clause had a dead branch anyway:
  `RateLimiter::consume` moves `last_updated_at` and `next_seq` in
  lockstep, and `revert` rolls both back, so they cannot diverge.

No proto, guardian-side, or `guardian_limiter.rs` changes. Non-guardian
baseline is untouched.
The two-variant enum was a glorified bool — the retry loop only checked
for FullySeeded. Switch to bool to drop the indirection.
The synchronous seed in start() was best-effort (return ignored) and
duplicated the retry task. Drop it and let the background task own
seeding, with the first attempt firing at delay = 0. No caller relies
on guardian state being populated before start() returns.
Replace the hand-rolled exponential-backoff loop with backon's
ExponentialBuilder + Retryable, matching the pattern already used in
crates/hashi/src/communication/timeout_and_retry.rs. Also bump the
GetGuardianInfo failure log from debug to warn so operators see
unreachable-guardian failures at the default log level.
Match the file's convention of short or no doc comments. Function
names and bodies carry the meaning here.
snap_to was a recovery primitive the new design doesn't need — the
local limiter is bootstrapped from the guardian at startup and
advanced only on accepted consumes, with no force-resync path. Drop
the function, its test, and the references in module and apply_consume
docs.

Also trim docstrings that just restated method names and inline test
arithmetic that re-derived constants from the test setup.
Each committee member will validate against a leader-supplied seq at
MPC signing time, so `validate_consume` becomes a pure check rather
than a peek-and-return. Returns `()` on success; the seq mismatch case
shares the existing `SeqMismatch` variant (with `local`/`incoming`
field names that read naturally for both validate and apply).
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-local-limiter branch from 7a8f7bf to 4bf13f8 Compare April 28, 2026 22:07
@0xsiddharthks 0xsiddharthks enabled auto-merge (squash) April 28, 2026 22:13
@0xsiddharthks 0xsiddharthks merged commit 9ffd4b8 into main Apr 28, 2026
5 checks passed
@0xsiddharthks 0xsiddharthks deleted the siddharth/guardian-local-limiter branch April 28, 2026 22:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants