Skip to content

Classify Soroban simulation failures and support state restoration - #255

Closed
deslawson wants to merge 3 commits into
Dione-b:mainfrom
deslawson:main
Closed

deslawson wants to merge 3 commits into
Dione-b:mainfrom
deslawson:main

Conversation

@deslawson

@deslawson deslawson commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Added distinct errors for missing accounts, resource or fee limits, and archived entries. Added opt-in restore: true for invoke() and documented the restore flow.

Motivation

Mainnet simulation failures for unfunded accounts, resource or fee limits, and archived entries were surfaced as generic errors with little recovery guidance.

Behavior

Added distinct CAATINGA_* error codes and actionable hints. invoke() now supports opt-in { restore: true }, forwarding the restore option and wallet signer to compatible bindings. Updated client docs with automatic and manual restore guidance.

Tests

Added tests for error classification, account lookup failures, and restore option forwarding. Could not run tests or typecheck: pnpm is unavailable, and Corepack could not initialize its cache in the restricted environment.

Release Impact

Checklist

  • Public docs updated when behavior changes.
  • CAATINGA_* errors added or updated when public failures change.
  • --source examples use a local Stellar CLI identity, not a public G... address.
  • Version, lockfile, and workflow pins are aligned when tooling changes

Closes #240
.

Added distinct errors for missing accounts, resource or fee limits, and archived entries. Added opt-in restore: true for invoke() and documented the restore flow.

Closes Dione-b#240
@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@deslawson Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Dione-b Dione-b left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! The restore: true plumbing works with stellar-sdk 16 (method options override client options, so restoreFootprint picks up the signer), and ACCOUNT_NOT_FOUND is classified correctly. A few things need fixing before merge. I ran the suites locally, since you mentioned you couldn't:

Blocking

  1. Tests fail: 2 failures in invoke-args.test.ts. splitInvokeArgsAndOptions now returns restore: false and the toEqual assertions weren't updated.

  2. Conflicts with main (#267, assertWalletNetwork). When rebasing, keep assertWalletNetwork before createTransaction. With restore: true the RestoreFootprint tx is signed inside callMethod, so the wrong order lets the wallet sign on the wrong network.

  3. Archived and resource-limit errors aren't classified in the real SDK flow. AssembledTransaction has no prepare(), and its simulate() doesn't throw on simulation or restore errors. They surface when the .result getter is read (ExpiredStateError / SimulationFailedError) inside readSimulationResult, which isn't wrapped. On invoke() they surface inside signAndSend, which becomes XDR_SUBMIT_FAILED. I reproduced a read() on archived state rejecting with a raw ExpiredStateError. Please classify those paths too, and test with a fake whose simulate() resolves and whose result getter throws, not only a rejecting prepare().

  4. False positives in the classifier (reproduced):

    • "...status code 429: rate limit exceeded" → SIMULATION_RESOURCE_LIMIT (/LIMIT.?EXCEEDED/ is too broad).
    • { error: "boom", restorePreamble: null } → SIMULATION_ENTRY_ARCHIVED, because collectErrorText pushes the field names into the text it matches against.

    Please drop field names, check restorePreamble != null directly, and anchor the patterns to known Soroban/RPC strings. The whole-object JSON.stringify makes the loose patterns riskier.

Should fix

  • A contract arg literally named restore (invoke("set_flag", { restore: true })) is now treated as options. Document this or tighten the heuristic.
  • Wrapping callMethod changes existing behavior: any non-Caatinga error (e.g. arg conversion) now becomes XDR_PREPARE_FAILED "Failed to simulate", including in read(). Please note this in the changeset / Release Impact.
  • The hint and docs say restorePreamble is kept on error.cause. That isn't true for SDK bindings: ExpiredStateError carries only a message.

Nits

  • Run pnpm format: docs/errors.md and packages/client/src/xdr/build-xdr.ts fail Prettier.
  • xdr/build-xdr.ts now imports from client/. Consider moving the classifier to a neutral module.
  • Please add tests for the restore signer's XDR_SIGN_FAILED paths (empty result or rejection).
  • Please open PRs from a feature branch rather than your fork's main.

@Dione-b Dione-b left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sticking with this. I checked out deslawson:main locally. The two "Merge branch 'main'" commits since the last review resolved the conflicts incorrectly, and the branch no longer builds:

1. Typecheck fails (blocking). pnpm typecheck reports 8 errors in packages/client/src/client/caatinga-contract-client.ts. The createClient/callMethod block from createTransaction ended up inside resolveWalletPublicKey (around lines 265-285), where contractId, method, args and methodOptions are not in scope, and options is undefined around lines 220-221. pnpm test fails across createCaatingaClient and splitInvokeArgsAndOptions (for example ReferenceError: options is not defined).

2. The wallet network check is gone. assertWalletNetwork (#267) no longer exists on this branch, and invoke() doesn't call it. With restore: true, the RestoreFootprint transaction could then be signed for the wrong network. It must run before createTransaction, as on main.

3. Previous review points are still open: the invoke-args tests for the new restore option, classifying errors thrown from the .result getter (readSimulationResult / signAndSend), the /LIMIT.?EXCEEDED/ false positives and matching on field names such as restorePreamble, and a contract argument named restore being taken as options.

4. Compatibility note. Failures that used to surface as CAATINGA_XDR_PREPARE_FAILED now come out as SIMULATION_ACCOUNT_NOT_FOUND, SIMULATION_RESOURCE_LIMIT or SIMULATION_ENTRY_ARCHIVED. Adding codes is fine, but please say in the changeset that callers branching on the old code will see the new ones.

Suggested path: open a fresh branch from current main (not your fork's main), re-apply the feature commits on top, and run pnpm typecheck && pnpm test before pushing. Happy to re-review after that.

@Dione-b

Dione-b commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Thanks for this work, @deslawson. Since the branch stopped building after the merges from main and the review went unanswered, I rebuilt the feature on current main in #311. You're credited there as co-author. It keeps your design: the three new simulation codes, invoke(..., { restore: true }) and the restore signer. It also addresses the review points: the wallet network check runs before restore, the classifier is anchored to real RPC/SDK strings, and it covers result getter / signAndSend errors. Closing this one in favor of #311. Feedback there is very welcome.

@Dione-b Dione-b closed this Oct 20, 2026
Mukhtar003 pushed a commit to Mukhtar003/caatinga that referenced this pull request Oct 21, 2026
…estoration

Rebuilds Dione-b#255 by @deslawson on current main.

- New public codes: CAATINGA_SIMULATION_ACCOUNT_NOT_FOUND,
  CAATINGA_SIMULATION_RESOURCE_LIMIT, CAATINGA_SIMULATION_ENTRY_ARCHIVED.
- Classifier lives in packages/client/src/errors/simulation-error.ts and
  matches only field values (never field names) against strings Soroban
  RPC, stellar-core and stellar-sdk emit, plus a non-null restorePreamble,
  so "429: rate limit exceeded" is no longer a resource limit.
- Errors thrown by the binding's result getter (read/simulate) and inside
  signAndSend (invoke) are classified; unknown errors raised while the
  binding builds the method propagate unchanged.
- invoke(method, args, { restore: true }) forwards restore and a wallet
  signer to the binding; assertWalletNetwork still runs before
  createTransaction, and restore signing failures map to XDR_SIGN_FAILED.
- Changeset documents the code changes for callers that branched on
  XDR_PREPARE_FAILED / XDR_SUBMIT_FAILED.

Closes Dione-b#240

Co-authored-by: Lex0865 <destinylawson007@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

client: all simulate failures collapse to XDR_PREPARE_FAILED; restore/archival not handled

2 participants