mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
feat: Grin slatepack relay (non-custodial async mailbox, feature-flagged)
Final wallet-handler slice. An async store-and-forward mailbox for interactive
Grin transfers between two registered Smirk users, over the grin_slatepacks db.
Reworked clean from the legacy relay — NOT a port.
Endpoints (under /api/v1/wallet/grin/relay, JWT-gated):
- POST create {recipient_user_id, slate_id, slatepack, amount_nanogrin}
- GET pending -> the caller's inbox
- POST get {slate_id} -> poll a relay's state (sender/recipient)
- POST respond {slate_id, response_slatepack} (recipient)
- POST finalize {slate_id, tx_hash} (sender; RECORDS the txid only)
- POST cancel {slate_id} (sender or recipient)
Non-custodial: stores only opaque (encrypted) slatepacks + lifecycle status;
never holds keys, finalizes, or broadcasts. The wallet broadcasts locally (via
/wallet/grin/broadcast) and reports the txid here — diverging from the legacy
server-side post_tx (see docs/private/OWN_BACKEND_CONVERGENCE.md).
Controls + hardening (incl. fixes from the adversarial review workflow):
- Per-action authz: caller must be the slate's sender/recipient; a non-party
gets 404 (no existence oracle).
- slate_id is UNIQUE (+ validated as a UUID) and mutations key on the
authorized row's PRIMARY KEY, not slate_id — so a duplicate slate_id can't
brick a relay (fetch_optional-on-2-rows 500) or let a slate_id-keyed UPDATE
touch another row. create maps the unique violation to 409.
- State transitions are ATOMIC: the status + expiry preconditions live in the
UPDATE WHERE clause (not a Rust read-check-write), so respond-twice and
cancel-races-finalize cannot occur; a non-qualifying transition -> 409.
- TTL enforced at the action boundary (respond/finalize require expires_at >
now) + a periodic expire_old_slatepacks sweep in main.rs (relay-enabled only).
- Recipient must be a registered user; slatepack sizes capped (64 KiB).
- Behind FEATURE_GRIN_RELAY (default on).
6 paths + schemas + grin_relay tag in openapi (drift-clean). 4 L1 integration
tests against live Postgres: full lifecycle, unregistered-recipient 400, auth
gate, non-party/non-recipient 404s.
This commit is contained in:
@@ -84,7 +84,10 @@ CREATE INDEX idx_user_keys_user_id ON user_keys (user_id);
|
||||
-- ── grin_slatepacks ─────────────────────────────────────────────────────────
|
||||
CREATE TABLE grin_slatepacks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
slate_id TEXT NOT NULL,
|
||||
-- UNIQUE: the relay's authorization + state machine key off slate_id, so it
|
||||
-- must map to exactly one row (a collision would let mutations keyed on
|
||||
-- slate_id touch another user's relay). Also serves as the lookup index.
|
||||
slate_id TEXT NOT NULL UNIQUE,
|
||||
sender_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
recipient_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
recipient_address TEXT,
|
||||
@@ -98,7 +101,7 @@ CREATE TABLE grin_slatepacks (
|
||||
finalized_at TIMESTAMPTZ,
|
||||
tx_hash TEXT
|
||||
);
|
||||
CREATE INDEX idx_grin_slatepacks_slate_id ON grin_slatepacks (slate_id);
|
||||
-- (slate_id lookup index is provided by the UNIQUE constraint above.)
|
||||
CREATE INDEX idx_grin_slatepacks_sender ON grin_slatepacks (sender_user_id);
|
||||
CREATE INDEX idx_grin_slatepacks_recipient ON grin_slatepacks (recipient_user_id);
|
||||
CREATE INDEX idx_grin_slatepacks_status ON grin_slatepacks (status);
|
||||
|
||||
Reference in New Issue
Block a user