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

Abierto
#609 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
typescript

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
TypeScript
Estrellas
2
Forks
5
Merge medio
23 h 33 min
PR fusionados (30 d)
87

Preparar el entorno

Aún no hemos revisado los archivos de configuración de este proyecto. Empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de block52/ui

Todos los issues de block52/ui

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.