OpenZeppelin / OpenZeppelin/guardian
Per-account proposal handshake cost grows with interleaved proposal traffic (send + consume) — +128–153% over 300 ops
@zeljkoX is already working on this.
Since Jul 17, 2026.
- Dominant language
- Rust
- Stars
- 10
- Forks
- 20
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 43
Description
Environment
guardian.openzeppelin.comv0.15.1 (git 82225104c8a2), Miden testnet- Paired A/B stress runs: identical PRNG seed, 300 guardian-cosigned sends, per-transaction stage timing. Single variable: the receiving wallet either also issued consume (claim) proposals for ~50% of received notes, or none at all.
Observation
With interleaved consumes, the guardian handshake stages grow monotonically over the run; removing consume traffic flattens exactly those stages:
| stage | growth Q1→Q5, claims on | claims off |
|---|---|---|
| proposal-create → signing | +153% | +77% (non-monotonic) |
| signing → sending | +128% | +41% |
| sending → submitting | +43% | 0% |
Everything else was measured flat in both runs (client sync, proving, block wait, local execution, UI). Net effect: median send latency grows from ~11 s to ~19 s across the run, driven by these stages. Since the flattening tracks the traffic-mix knob and not client state, session age (survives page reloads), or network (per-call durations flat), the growth follows per-account proposal co-traffic.
This looks like the non-catastrophic sibling of #303: #312 fixed the multi-minute stall/409-churn form; the steady per-account cost growth under normal interleaved traffic remains.
Where the time goes (narrowing measurements)
- Server per-call response times are flat. Guardian
delta/proposalendpoints answered in ~250–280 ms at the start AND end of the runs; sync RPCs pinned at 201–215 ms. The growth is not server response latency per request. - The number of round-trips per proposal grows. In a heavier run on the same account shape (2026-07-13, 442 ops), client-issued guardian proposal-path calls grew from 7.5 to 12.2 per operation while per-call durations stayed flat — the handshake takes more exchanges, not slower ones.
- Account payload grows.
GetAccountwas the only RPC whose duration grew (305 → 483 ms) over that run, tracking a growing account state payload. - It is not client-session state. Full page reloads of both wallets at ops 100 and 200 produced no reset (+0.8% / +5.9% across the boundaries) — whatever accumulates is durable/server-side, not in-memory client state.
- It is not queue depth at send time. The sender's local tx queue was empty at the start of nearly every op; latency ramps even for ops issued with zero in-flight work.
Suspected mechanism (falsifiable)
Per-proposal work appears to scale with the account's accumulated delta/proposal history: each handshake re-fetches or re-derives per-account state whose size grows with every canonicalized delta, and interleaved consume proposals both add history faster and add a canonicalization cycle (~10–12 s, #316) that overlaps the next send's handshake. Two checks would confirm or kill this: (1) do get_deltas/state-fetch response sizes grow linearly with canonicalized-delta count? (2) does per-proposal server-side work (DB reads, verify passes) scale with history length? If both are flat, the growth must be in the client-side multisig handshake logic instead — we can instrument that side next.
Reproduction
Two guardian-backed accounts on testnet, v0.15.1. Alternate ~300 sends between them; receiver claims ~50% of notes (each claim = a consume proposal on the receiving account). Measure proposal-create → submit per transaction. Expected: those stages roughly double by op ~300. Control: same seed with claims disabled — the submit stage stays flat and the others grow at less than half the rate. Full per-stage dataset (both runs, per-transaction timestamps) available — happy to attach.
References
#303 / #312 · #316 (post-send canonicalization wait) · #317 (scalability umbrella)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.