You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e7e0525
Browse filesBrowse the repository at this point in the historyBrowse files
* fix(quota): atomic reserve to close the check-and-commit race
saveQuota was made atomic (temp + rename) to stop torn reads (#309), but
enforcement was still three separate async steps: checkQuota, then
storage.write, then updateQuotaUsage. Two concurrent writers could both pass
the check before either recorded its usage, overshooting the pod limit; and
the read-modify-write in updateQuotaUsage could lose updates outright.
Add reserveQuota — check-and-commit under a per-pod async lock — and route
the PUT/POST write paths through it, releasing the reservation if the write
fails. updateQuotaUsage now takes the same lock, fixing the lost-update on
the delete/shrink path. checkQuota is kept as a cheap lock-free pre-check.
Found while hardening the Rust parity port solid-pod-rs (v0.5.0-alpha.8),
which added QuotaPolicy::reserve for the same reason.
* fix(pay): idempotent deposit credit to close a double-credit window
The GET /pay/.balance auto-scanner credited the ledger (writeLedger) and then,
in a separate write, recorded the funding UTXO as seen (saveUtxos). A crash
between the two writes lost the seen-record; because the scanner re-runs on
every balance poll, the same on-chain UTXO was then credited again on the next
request — minting balance from nothing.
Record each deposit's idempotency key (chain:txid:vout) inside the ledger via
creditOnce, so the balance increment and the "already counted" marker commit
together in the single authoritative writeLedger. saveUtxos becomes an advisory
cache: losing it can no longer double-credit. Legacy ledgers gain the `credited`
array on read.
Found while hardening the Rust parity port solid-pod-rs (v0.5.0-alpha.8),
which consolidated ledger + replay-set + order-book + exchange into one atomic
state.json commit for the same reason.
* fix(pay): persist the utxo cache when an outpoint is appended
Review follow-ups on the hardening branch.
1. fix(pay) — the saveUtxos call was gated on `credited > 0`, but in the
crash-then-rescan case this branch exists to fix, creditOnce returns
didCredit=false, so nothing was credited and the cache write was skipped.
The appended outpoint was then never persisted and the next
GET /pay/.balance re-queried the explorer for it — repeating on every poll
until an unrelated deposit happened to trigger a save. Gate saveUtxos on a
new `appended` flag instead, so the cache self-repairs. Ledger-first
ordering is kept: a crash between the two writes must leave the credit
recorded (no double-credit), never the reverse (under-credit).
2. test(webledger) — 'the credited marker survives a ledger read migration'
never called readLedger; it re-implemented the guard inline and asserted
typeof readLedger === 'function'. Since creditOnce also self-heals the
field, the test passed regardless of the migration. Replaced with two tests
that go through real storage: a legacy ledger gains credited: [] on read
(asserted before any creditOnce call), and a persisted marker survives a
write/read round trip. Verified: deleting the migration line from
readLedger now fails the first test.
3. webledger — createLedger seeds credited: [] so a new ledger has the same
shape the migration produces for a legacy one.
4. quota — the reserveQuota docblock described checkQuota as a retained
pre-check, but no production caller invokes it after this change. Reworded
so nobody hunts for a pre-check that is not there.
Full suite: 1065/1065 pass.
---------
Co-authored-by: Melvin Carvalho <melvincarvalho@gmail.com>
0 commit comments