Skip to content

[5/n][guardian-integration] soft-reserve RPC + pre-commit integration#464

Closed
0xsiddharthks wants to merge 1 commit intosiddharth/guardian-wid-cachefrom
siddharth/guardian-soft-reserve
Closed

[5/n][guardian-integration] soft-reserve RPC + pre-commit integration#464
0xsiddharthks wants to merge 1 commit intosiddharth/guardian-wid-cachefrom
siddharth/guardian-soft-reserve

Conversation

@0xsiddharthks
Copy link
Copy Markdown
Contributor

@0xsiddharthks 0xsiddharthks commented Apr 17, 2026

Stacked on #463#423#466#449.

Summary

PR-2 (hard reserve post-MPC) means an over-limit withdrawal only surfaces AFTER we've done committee fan-out + MPC signing — minutes of wasted work per leader iteration. This PR adds a lightweight pre-commit soft reserve so over-limit withdrawals abort cheaply.

Guardian changes (hashi-types + hashi-guardian)

  • RateLimiter gains pending_reserves: HashMap<wid, PendingReserve> and a soft_reserve(wid, timestamp, amount, now) method. Idempotent on wid (repeat probes return the existing reservation with a refreshed TTL). Capacity check: refill_capacity(timestamp) - Σ pending.amount_sats ≥ amount.
  • consume now takes wid and drops any matching pending entry — a hard reserve atomically converts a soft reserve.
  • expire_pending(now) drops stale entries; SOFT_RESERVE_TTL_SECS = 5 min. A 1s sweep task runs in `main.rs`.
  • New SoftReserveWithdrawal RPC. No committee signature required for soft reserves — the TTL bounds DoS blast radius and wid idempotency handles retries.

Hashi changes

  • GuardianClient::soft_reserve_withdrawal wrapper.
  • leader::soft_reserve_withdrawal_through_guardian probes the guardian after coin selection and before the committee BLS fan-out for commit. Rate-limited / unavailable → abort iteration; next tick retries with the same wid (guardian returns the same reservation).
  • compute_withdrawal_wid extracted into `withdrawals.rs` so both touchpoints derive the same deterministic id.

Move package

No changes.

Tests

  • test_soft_reserve_is_idempotent_on_wid — repeat probes return existing reservation, TTL refreshed
  • test_soft_reserve_rejects_over_commitment_across_wids — pending amounts subtract from capacity
  • test_soft_reserve_enforces_monotonic_timestamp — out-of-order timestamps rejected
  • test_expire_pending_drops_stale_reservations — TTL sweep drops past-due entries
  • test_consume_removes_matching_soft_reserve — hard reserve atomically drops pending
  • test_soft_reserve_leaves_room_for_hard_reserve_of_same_wid — end-to-end: soft-reserve capacity, then hard-reserve succeeds
  • Existing limiter + withdrawal tests updated for the new consume(wid, seq, …) signature

Follow-ups

  • Automated e2e harness that spins up a provisioned guardian in-process (the "full flow with guardian running" check the user requested).
  • Guardian restart safety: rehydrate LimiterState + pending/recent maps from S3 on boot (PR-5 in the stack).

The hard-reserve path added in #423/PR-2 only debits the bucket AFTER
MPC — which means a leader can spend minutes doing committee fan-out
+ MPC signing only to discover the guardian is out of capacity. Add a
pre-commit soft reserve so that over-limit withdrawals abort cheaply
and do not churn leader iterations.

Guardian side (`hashi-types` + `hashi-guardian`):
- Extend RateLimiter with pending_reserves: HashMap<wid, PendingReserve>,
  a soft_reserve method (idempotent on wid, monotonic timestamp,
  capacity = refill - sum(pending)), and expire_pending for TTL sweep
  (SOFT_RESERVE_TTL_SECS = 5 min).
- consume now takes wid as well and drops any matching pending entry so
  a hard reserve converts the soft reserve atomically.
- Add SoftReserveWithdrawal RPC and handler. Soft reserves do not
  require a committee signature — the TTL bounds DoS blast radius and
  wid idempotency handles retries.
- Spawn a 1-second TTL sweep task in main so expired reservations free
  capacity promptly.

Hashi side:
- Add GuardianClient::soft_reserve_withdrawal wrapper.
- In leader::process_approved_withdrawal_request_batch, probe the
  guardian immediately after coin selection (build_withdrawal_tx_
  commitment) and before the committee BLS fan-out for commit.
  Rate-limited / unavailable aborts the iteration; next leader tick
  retries with the same wid so the reservation is reused.
- Extract compute_withdrawal_wid helper so both touchpoints derive
  the same deterministic identifier from request_ids.

Move package: no changes.
@0xsiddharthks 0xsiddharthks force-pushed the siddharth/guardian-soft-reserve branch from cadedf6 to 791b34a Compare April 23, 2026 10:17
@0xsiddharthks 0xsiddharthks changed the title [4/n][guardian-integration] soft-reserve RPC + pre-commit integration [5/n][guardian-integration] soft-reserve RPC + pre-commit integration Apr 23, 2026
@0xsiddharthks
Copy link
Copy Markdown
Contributor Author

Folded into #423. The soft pre-MPC and hard post-MPC guardian touchpoints now ship together as a single MVP integration PR. See .claude/plans/golden-finding-castle.md for the full rationale.

The soft-reserve commit (791b34a) was cherry-picked onto siddharth/guardian-integration with the get_cached_response short-circuit stripped (that code path depended on #463). Everything else moved verbatim: pending_reserves / PendingReserve / SOFT_RESERVE_TTL_SECS, soft_reserve / expire_pending, SoftReserveWithdrawal RPC, run_soft_reserve_sweep 1s tick, and leader-side soft_reserve_withdrawal_through_guardian.

Branch siddharth/guardian-soft-reserve preserved locally for reference.

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.

1 participant