Submission confirmation is not tied to transaction identity — silent failures and possible duplicate actions

Aperta
#609 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
typescript

Direzione di ricerca

Start with src/submit/confirmationGate.ts, ActionSubmitController.ts, and ActionSubmitController.test.ts:156, then trace optimistic updates in bus/ingest.ts and retries in src/utils/cosmos/client.ts:281-303 and joinTable.ts:66. Run the existing submission tests first; done means confirmation and failure states are tied to transaction identity, optimistic movement cannot confirm a job, and uncertain retries do not create duplicate actions.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

From the external repository review (10 Sep 2026). Both halves verified against HEAD. Filed as one issue because they share a root cause and a fix: nothing in the submission path is tied to transaction identity.

a) Table progress is treated as confirmation

src/submit/confirmationGate.ts:41-49:

const countAdvanced = hasValue(current.actionCount) && current.actionCount > baseline.actionCount;
const indexAdvanced = nextActionIndex(current) > baseline.actionIndex;
const handAdvanced  = hasValue(current.handNumber) && current.handNumber > baseline.handNumber;
return countAdvanced || indexAdvanced || handAdvanced;

All three are table-global counters. Any other player acting confirms our submission. The controller never compares job.hash to anything — it is assigned at ActionSubmitController.ts:199 and used only to call onSuccess.

The 8s timeout also marks the job confirmed, not unknown (:245-254 → confirmJob → status = "confirmed"). The timer starts at runJob entry, before broadcast, so it covers signing + broadcast + block time.

And a later rejection is then discarded (:207-210): isTerminal(job) is true for "confirmed", so a real chain rejection arriving afterwards is swallowed — no toast, no onError.

⚠️ Optimistic snapshots feed the same path. bus/ingest.ts:33 classifies optimistic as kind: "state", and those go straight onto the logical track that the controller reads. So a mempool projection of someone else's pending action can confirm our submission.

b) Uncertain failures cause fresh submissions

ActionSubmitController.ts:219-233 retries on transport errors. There is a guard — the reviewer under-stated this — but it is the same weak gate above, given only gateSettleMs: 250ms to observe a chain that commits in ~5s.

The retry re-signs from scratch: clearSigningCache() discards the memoized client, so the retry rebuilds the wallet and re-queries the account sequence.

The precise dangerous window: if the first tx is still pending, the re-query returns the same sequence and the identical bytes get deduped — harmless. If the first tx committed between the failure and the retry, the re-query returns sequence N+1 and produces a genuinely distinct, valid second action.

src/utils/cosmos/client.ts:281-303 (withMoneyMoverRetry, used by joinTable.ts:66) does the same with no confirmation gate at all, on a money-moving path.

Why this matters more than it looks

Combined with poker-vm#2609 — where the SDK returns a transaction hash for failed execution — there is currently no layer that can tell a user their action failed. The SDK reports failure as a hash; the UI confirms on unrelated table movement or an 8s timer; and any late rejection is discarded as moot.

Suggested plan

  1. Model the states honestly: submitted, accepted, committed-success, committed-failure, unknown. A timer expiring produces unknown, never confirmed. That single change removes the silent-failure class.
  2. Match by transaction identity. Correlate the committed receipt by hash rather than by counters. performActionSync already returns a hash; the WS push should be reconcilable against it.
  3. Keep optimistic state visually distinct from authoritative state and never let it satisfy a confirmation.
  4. On uncertain transport failure, query before resubmitting — and rebroadcast identical signed bytes rather than re-signing, so a duplicate is impossible rather than merely unlikely. Preserve the signed bytes and hash for that purpose.
  5. Serialize account-level submissions across the action path and withMoneyMoverRetry, so two flows cannot race on the same sequence.
  6. Tests exist for the current behaviour (ActionSubmitController.test.ts:156 codifies the 8s escape hatch as intended) — those need updating deliberately, not silently.

Original review findings 7 and 8.

Lingua principale
TypeScript
Stelle
2
Fork
5
Merge medio
22h 35m
PR unite (30g)
91

Preparare l'ambiente

Non abbiamo ancora controllato i file di configurazione di questo progetto. Parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di block52/ui

Tutte le issue di block52/ui

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.