Skip to content

fix(provider-workflows): return only status and hash from the getTransaction activity - #383

Merged
jorgecuesta merged 1 commit into
stagingfrom
fix/provider-tx-status-projection
Oct 23, 2026
Merged

jorgecuesta merged 1 commit into
stagingfrom
fix/provider-tx-status-projection

Conversation

@jorgecuesta

Copy link
Copy Markdown
Contributor

Closes #356.

Change

  • ExecuteTransaction (provider-workflows) reads only status and hash from the getTransaction activity. A new DAL method getTransactionStatus selects just those two columns, and the activity returns that projection.
  • The activity name is unchanged, so in-flight workflows replay as before. Their recorded results (full rows) and the new minimal results both carry status and hash, the only fields the workflow reads.
  • The full-row DAL getTransaction, used by signing and broadcast, is unchanged.

Verification

  • New activity tests (real DAL over a fake DB) and DAL tests. The activity test fails on the current code and passes with the change, as does the not-found case.
  • provider-workflows test and check-types green.

…saction activity

ExecuteTransaction reads only status and hash from this activity, so a new DAL method selects just those two columns and the activity returns that projection. The activity result recorded in workflow history no longer carries the rest of the row. The activity name and the full-row DAL method used by signing and broadcast are unchanged.
@jorgecuesta jorgecuesta added bug Something isn't working release Trigger staging deploy on merge to staging labels Oct 23, 2026
@jorgecuesta
jorgecuesta merged commit aae3228 into staging Oct 23, 2026
7 checks passed
@jorgecuesta
jorgecuesta deleted the fix/provider-tx-status-projection branch October 23, 2026 21:15
@github-actions github-actions Bot mentioned this pull request Oct 23, 2026
jorgecuesta added a commit that referenced this pull request Oct 23, 2026
* fix(temporal): stop the watchdog re-healing a schedule right after a ladder reset (#370)

resetLadder re-baselines lastActionCount to the current count and zeroes injectedTriggers, so until the next fire there is no growth, hasAutonomousFire is false, and evaluateLiveness fell back to createdAt and judged the schedule stale, consuming a heal attempt and then resetting again on the next fire; any schedule whose interval outlasts the watchdog tick flapped this way after one episode. With no injected triggers outstanding the latest takenAt is a real fire, so use it as the reference; the createdAt fallback still applies while our own triggers are the only growth.

* fix: stop unbroadcastable transactions from looping or wedging the middleman dispatcher (#368)

* fix: refuse empty transaction payloads at the wallet and in the server actions

Soothe returned a body with an empty transactionHex and the app stored it as a signed Unstake (tx 519 on mainnet), which could never be broadcast. assertSignedTransaction (@igniter/ui/lib/signedTransaction) rejects a blank or non-string signedPayload/unsignedPayload in the wallet context's signTransaction, for every adapter, so the signature step fails and the user can retry, and again in CreateStakeTransaction and CreateUnstakeTransaction before inserting.

* feat(db): forbid blank transaction payloads with NOT VALID CHECK constraints

Migration 0020 adds CHECK (btrim(...) <> '') on signedPayload and unsignedPayload as NOT VALID, so existing rows (only failed row 519 on mainnet) are left alone. The middleman and middleman-workflows initContainers can run it at once, so it takes a self-conflicting table lock and adds each constraint only if missing; without the lock the second run fails with 'already exists'. migrations.test.ts checks this on a local database named *test*, which it only resets, never creates or drops; scripts/test-migrations.sh runs it in Docker.

* fix(middleman-workflows): fail a tx with a missing payload instead of looping, anchor by CAS

persistBroadcastAnchor threw 'Transaction is not signed' for an empty payload, so ExecuteTransaction never reached its terminal branch and the dispatcher relaunched it forever (tx 519). The throw is gone: deriveTxHash already rejects blank input and its catch returns null, which fails the row with its usual effects. The anchor is now written only while the row is pending with no hash; losing that CAS throws a non-retryable transaction_not_pending, which the workflow treats as nothing left to do, so a run racing the dispatcher's give-up never broadcasts.

* fix(middleman-workflows): start ExecuteTransaction children instead of awaiting them, give up on stuck rows

The dispatcher awaited every child under a SKIP schedule, so one wedged run stopped dispatch for all transactions (#369), and a row failing for an unrecognised reason was relaunched forever (tx 519). Behind patched('execute-pending-dispatch-v3') it now lists only pending rows with no hash, starts children with ABANDON and a 15m workflowExecutionTimeout, and counts one attempt per run id on ticks where the RPC answered (migration 0021). A row with 5 attempts over an hour is failed through a pending-and-no-hash CAS, then the provider and user are notified.

* fix(middleman-workflows): list never-tried rows first in the dispatcher queue, cap it at 100

Ordered by id alone, the 100 oldest pending rows with a child in flight or failing filled every tick's list, so a new transaction waited until the give-up removed them, an hour or more; listDispatchable now orders by dispatchAttempts then id. The cap drops from 500 to 100 and its comment says what it bounds: one dispatcher run's history and list payload, not duplicates, which the child workflow id already prevents. A new real-Postgres test (run by scripts/test-migrations.sh) proves new rows are not held back, a 250-row backlog drains with each row listed once, and failing rows are still reached and given up; two of its three cases fail with the old id-only order.

* feat(middleman-workflows): durable failure effects for failed stake transactions (#376)

* feat(db): add the failure-effects outbox columns to transactions (migration 0022)

failureEffects (pending|releasing|done|manual, CHECKed), failureReleaseAddresses (jsonb, NULL or an array, CHECKed) and failureNotifiedAt, all NULL for existing rows so nothing is owed and nothing is backfilled. Hand-written in the 0020/0021 style (lock plus IF NOT EXISTS) so the two initContainer migrators can apply it at once; migrations.test covers the columns, the CHECKs and the concurrent apply.

* feat(middleman-workflows): add the failure-effects outbox DAL

Each method is one guarded statement: the three *WithEffects failure CAS write the outbox in the same UPDATE as the status flip, beginRelease resolves the release entry table (pending proceeds, releasing goes manual, anything else does nothing), the outcome moves are CAS on their source state, claimNotify is the at-most-once notice fence, and listFailureEffects returns rows owed a release or a notice. A real-Postgres test, wired into scripts/test-migrations.sh, covers every transition, the lost ack, [] kept as [], the CHECKs and concurrent callers.

* feat(middleman-workflows): classify provider release outcomes

Add classifyReleaseError/classifyReleaseResponse and ProviderService.releaseSuppliersOutcome, which sends the same release POST as releaseSuppliers with a 20s AbortSignal timeout and returns ok, not-sent (an exact allow-list of connect, DNS and certificate codes on the undici cause chain, or any failure before fetch is called) or ambiguous (every other error, timeout or non-2xx status) without throwing, and cancels the response body. releaseSuppliers and its callers are unchanged.

* feat(middleman-workflows): add the failure-effects outbox activities

Add the *WithEffects claims, applyVerificationDecisionWithEffects (only an apply-failure Stake verdict changes: its CAS writes the outbox with the failed operators, [] when there are none, and it runs no pre-CAS release and no inline notify; every other verdict goes to the unchanged applyVerificationDecision), releaseFailedStake (nothing owed goes to done; otherwise the release entry table and the classified POST, and a lost outcome CAS logs the row's real state instead of claiming a release), markFailureEffectsExhausted, notifyFailedTransactionOwner (sends only when the fence CAS wins; the tx hash only for verifier rows) and listFailureEffects. The old activities are untouched.

* feat(middleman-workflows): add the TransactionFailureEffects workflow

Runs a failed row's outbox: notifyFailedTransactionOwner first (5s to 1m backoff, 30m budget), then releaseFailedStake (10s to 10m backoff, 24h budget). A notify or release ActivityFailure that is not a cancellation is logged and the workflow goes on; an exhausted release moves the row to manual. The DB row holds both fences, so re-entry is idempotent; later command changes need their own patched() marker.

* feat(middleman-workflows): route ExecuteTransaction failures through the outbox

Behind the execute-transaction-failure-outbox marker, evaluated in each terminal branch, the expired and unbroadcastable branches claim the row with claimUnbroadcastFailureWithEffects and the first-attempt rejection with claimBroadcastFailureWithEffects, through a proxy with no attempt cap and a 10m schedule-to-close, and run no release or notify: TransactionFailureEffects does both. A lost claim stands down as before. Histories recorded before the marker replay today's activities in today's order.

* feat(middleman-workflows): start failure-effects children from the dispatcher

Behind the execute-pending-failure-outbox marker, evaluated once per v3 run, every tick lists the rows owed failure effects before its early return and starts one TransactionFailureEffects child each at once (ABANDON, ALLOW_DUPLICATE, 25h), skipping runs still open; a failed listing or start is logged and never blocks dispatch or counts toward the fatal check. The give-up claims with claimUnbroadcastFailureWithEffects and runs no effects inline. Pre-marker v3 histories keep the old give-up.

* feat(middleman-workflows): apply verifier verdicts through the failure outbox

Behind the verify-pending-failure-outbox marker, evaluated once per sweep before the fan-out, every row is applied with applyVerificationDecisionWithEffects, so an apply-failure Stake verdict writes the outbox and leaves the release and notice to TransactionFailureEffects. Sweeps recorded before the marker keep calling applyVerificationDecision.

* fix: self-host Rubik and Overpass Mono so next build never fetches Google Fonts (#379)

next/font/google downloads the fonts during every `next build`, and when Google answers the CI or docker runner with an unexpected font URL the loader crashes ("Cannot read properties of null (reading '1')"), failing quality and deploy builds at random. Both apps now load the same latin variable woff2 files from src/styles/fonts (OFL licenses alongside) through next/font/local, with the same CSS variables, weight ranges, styles and display: swap; the unused middleman styles/layout.ts, the last next/font/google import, is removed.

* fix(db): redact transactions params in SQL debug logs (#380)

Queries on transactions log their params as redacted at debug level, like the other sensitive tables; packages/db gets a jest setup with tests for the query-param redaction.

* feat(ui): open transaction hashes in the Pocket explorer (#381)

Transaction hashes were styled as links but clicking them did nothing. Each app's application settings provider now passes its chainId to a shared ChainIdProvider, and TransactionHash renders an explorer link for pocket (explorer.pocket.network/tx) and for pocket-beta and pocket-lego-testnet (the explorer's beta view), plain text on pocket-alpha, and keeps the in-app drawer where a caller passes onClick (NodeDetail).

* fix(middleman-workflows): tolerate single-provider failures in ProviderStatus (#382)

Settle the per-provider activities with Promise.allSettled and log each rejection, so one unreachable provider no longer fails the whole run. The run still fails with a non-retryable fatal_error when every provider failed. Scheduled activities are unchanged, so no patch gate is needed. Closes #286.

* fix(provider-workflows): return only status and hash from the getTransaction activity (#383)

ExecuteTransaction reads only status and hash from this activity, so a new DAL method selects just those two columns and the activity returns that projection. The activity name and the full-row DAL method used by signing and broadcast are unchanged.

---------

Co-authored-by: Jorge S. Cuesta <jorge.s.cuesta@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working release Trigger staging deploy on merge to staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant