feat(tx): surface pre-inclusion validity failures as typed TxValidityError#261
feat(tx): surface pre-inclusion validity failures as typed TxValidityError#261ReinhardHatko wants to merge 1 commit into
Conversation
…Error
A pre-inclusion validity/submission failure (e.g.
InvalidTransaction::Payment when the submitter can't pay or isn't
authorized) previously surfaced as an opaque error — either a base
TxError wrapping raw JSON (when polkadot-api rejected the subscription)
or a TxDispatchError whose message was the placeholder "unknown error"
(when a failure event arrived without a dispatchError; that field only
exists for an included-and-failed transaction).
- New `TxValidityError extends TxError` (inherits the SdkError marker)
carrying the raw failure payload on `reason` plus a human-readable
`formatted` string.
- polkadot-api's `InvalidTxError` subscription rejection — the path a
real validity failure actually takes — is now detected
(`isInvalidTxError`) and surfaced as err(TxValidityError) with the
decoded reason formatted via the new `formatValidityError` helper
({ type: "Invalid", value: { type: "Payment" } } → "Invalid.Payment").
- A failure event with no `dispatchError` (defensive) is classified by a
single `classifyFailure` helper shared by the best-block and finalized
paths; TxDispatchError is unchanged for the included-and-failed case.
- formatDispatchError describes the missing-dispatchError case instead
of returning "unknown error".
- `withRetry`'s `isNonRetryable` treats TxValidityError as
non-retryable, matching how these failures were classified when they
surfaced as TxDispatchError.
Implements proposal cluster E2 (error classification: opaque tx failures).
📦 Bundle size impactComparing
Thresholds — warn: ≥10% or ≥5.0 KB · fail: ≥20% or ≥15.0 KB (bundled). Percentage only applies once the baseline is ≥ 10 KB. |
| @@ -0,0 +1,32 @@ | |||
| --- | |||
There was a problem hiding this comment.
Please move the changeset in pending-changesets folder
| */ | ||
| function classifyFailure(event: { dispatchError?: unknown }, formatted: string): TxError { | ||
| return event.dispatchError == null | ||
| ? new TxValidityError(event, "validity/submission failure (no dispatch error)") |
There was a problem hiding this comment.
[nit] classifyFailure discards its formatted arg in the no-dispatchError branch and hardcodes a near-duplicate of formatDispatchError's string. Not a mismatch (both say the same thing) - just two literals to keep in sync. Consider reusing formatted or a shared constant.
| block: event.block, | ||
| }); | ||
| settleErr(new TxDispatchError(event.dispatchError, formatted)); | ||
| settleErr(classifyFailure(event, formatted)); |
There was a problem hiding this comment.
[nice to have] classifyFailure is called from the best-block (L162) and finalized (line 213) paths, where the tx was included - but it emits TxValidityError, whose message asserts "failed before inclusion." For that defensive branch a neutral message would avoid a misleading log. (The genuinely pre-inclusion path at L242 is correctly labelled.)
| export class TxValidityError extends TxError { | ||
| /** | ||
| * Raw underlying failure payload: the decoded validity error from | ||
| * polkadot-api's `InvalidTxError` (e.g. `{ type: "Invalid", value: |
There was a problem hiding this comment.
This looks like it is breaking the docs CI build.
The { type: "Invalid", value: { type: "Payment" } } example is split across two lines inside the backticks. The docs builder can't parse a code snippet that wraps, so it crashes on api/tx/index.mdx.
Fix: put the example on one line:
* polkadot-api's `InvalidTxError`
* (e.g. `{ type: "Invalid", value: { type: "Payment" } }`), or the failed
* tx event when the node reported failure without any payload.
What
A pre-inclusion validity/submission failure (e.g.
InvalidTransaction::Payment— submitter can't pay or isn't authorized) previously surfaced as an opaque error — either a baseTxErrorwrapping raw JSON, or aTxDispatchErrorwhose message was the placeholder"unknown error"(that field only exists for an included-and-failed tx).Change
TxValidityError extends TxError(inherits theSdkErrormarker), carrying the raw failure payload on.reason+ a human-readable.formatted.InvalidTxErrorsubscription rejection is now detected and surfaced aserr(TxValidityError)with the decoded reason ({ type: "Invalid", value: { type: "Payment" } }→"Invalid.Payment").withRetry'sisNonRetryabletreatsTxValidityErroras non-retryable (these are deterministic for the same account state).Implements proposal cluster E2 (error classification: opaque tx failures). Changeset included (tx minor + umbrella minor).
🤖 Generated with Claude Code