View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011934 | Taler | wallet (iOS App) | public | 2026-11-04 01:06 | 2026-11-04 13:01 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | MarcS | Reviewer | Christian Grothoff | ||
| Release signed by | MarcS | Deployed by | MarcS | ||
| Verifier | Communicator | ||||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | planning | Resolution | open | ||
| Product Version | 1.6 | ||||
| Target Version | 1.7 | ||||
| Summary | 0011934: Withdraw / Loading: expected wallet-core errors are titled "Internal core error" (e.g. 7054 withdrawal limit exceeded), with raw JSON as the body | ||||
| Description | Entering a withdrawal amount larger than the payment service allows is an ordinary, expected outcome. The wallet presents it as an internal fault: Internal core error { "when" : { "t_s" : 1793753531 }, "code" : 7054, "hint" : "withdrawal would exceed a hard limit of the exchange" } [ Copy the error JSON ] [ OK ] Code 7054 is WALLET_KYC_LIMIT_EXCEEDED: "the operation would exceed a limit imposed by the exchange that the user cannot raise by completing a KYC process". Nothing went wrong inside the wallet. The user is told they hit an internal error, and is offered a button to copy a JSON blob. Cause ----- TalerWallet1/Views/Main/ErrorView.swift maps exactly six wallet-core codes to meaningful titles: if code == 7002 || code == 7003 || code == 7005 || code == 7032 || code == 7036 || code == 7049 { ... } Every other code falls through to `walletCoreError`, which is String(localized: "Internal core error"). Both remaining branches use that title -- the hint branch taken in release builds and the JSON branch taken in developer mode -- so the title is wrong regardless of build configuration. Only the body differs: a release build shows the hint, a developer build shows the raw JSON seen above. Other codes in the same class are affected identically, among them 7055 WALLET_NO_SUITABLE_EXCHANGE, 7064 WALLET_CONTRACT_TERMS_UNSUPPORTED, and the two protocol-version incompatibilities 7000 and 7017. A user whose wallet is too old for an exchange is also told it is an internal core error. Observed on screen "Loading..." while a manual withdrawal was being confirmed (sheet 135, ManualWithdraw). The surrounding controls were "Withdraw Fr.", "Cancel", the error text above, "Copy the error JSON" and "OK". How to reproduce ---------------- By hand ------- 1. open the wallet, choose Withdraw for any currency 2. in the amount field enter a value far above the service's limit -- the exploration harness types 99999999999999999999 from its input corpus 3. confirm the withdrawal 4. the error view appears, titled "Internal core error" With the exploration harness ---------------------------- The harness found this while walking the withdrawal flow with hostile input. The episode is camp3-e00006; its observations contain the rendered text quoted above. To re-run that exact walk, from harness/ on a Mac with Xcode: python3 -m taler_fuzz build python3 -m taler_fuzz --max-steps 90 replay \ --tape "7,8,0,0,0,0,0,0,0,15,2,4,11,0,11,15,14,9,0,7,5,15,15,10,2,8,4,12,6,10,9,12,1,6,1,10,7,10,14,7,8,11,5,8,9,0,11,1,4,11,0,15,1,0,7,1,3,7,10,2,1,11,13,4,6,14,13,4,11,9,5,10,13,12,0,13,5,0,15,13,9,11,12,8,4,0,14,2,10,1" \ --label kyclimit --shots Note this walk reaches the condition against the live demo services, so it depends on their current limits. Deterministically, without needing the real condition ----------------------------------------------------- wallet-core's api-error experiment returns any code from any operation, and the harness wraps that: python3 -m taler_fuzz replay --tape "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" \ --label limit --fault core.err_limit --shots which delivers taler://dev-experiment/api-error?operation=getBalances &detail=<base64url {"code":7054,"hint":"..."}>&count=3 to the running app. The harness asserts that an error of this class is not presented with an internal-fault title; `core.err_no_exchange`, `core.err_proto_exchange`, `core.err_proto_bank` and `core.err_contract` cover the sibling codes. This needs the GNU_Taler or Taler_Nightly flavour, which are the ones that compile in the dev-experiment handler. Patch ----- Attached: fix-internal-core-error.patch (one file, +42), against master; also branch dev/phoenix-bot/fix-internal-core-error. It adds ErrorView.titleForExpected(code:), consulted before the generic branches, giving a title to the codes that are expected outcomes: 7054 WALLET_KYC_LIMIT_EXCEEDED -> "Limit exceeded" 7055 WALLET_NO_SUITABLE_EXCHANGE -> "No suitable payment service" 7064 WALLET_CONTRACT_TERMS_UNSUPPORTED -> "Transaction Error" (existing string) 7000 / 7017 protocol version incompatible -> "Incompatible version" An unrecognised code still falls through to the existing behaviour, so no other error can be made worse. The hint becomes the message, and the JSON stays available in developer mode only. Every code above was checked against taler-error-codes.ts rather than guessed. Two things for the maintainer to decide, which is why this is a proposal rather than a finished change: * the two new titles are new localizable strings and have no translations yet, so they would render in English until the catalogue is updated; * whether showing a raw JSON blob in developer mode is wanted at all on a user-facing error view, or whether it belongs behind "Copy the error JSON" alone. Verification status, stated plainly: the patch compiles for the simulator and its logic is a single switch consulted before the existing branches. I reproduced the defect itself from a real run, and I verified the injection channel used by the regression fault reaches wallet-core ("applying dev experiment ... api-error ... getBalances" in the app log), but I did not manage to re-provoke the exact on-screen presentation within a short walk, so I have not watched the patched title appear. A screenshot of the unpatched presentation is likewise not attached; the verbatim on-screen text is quoted above from the episode's recorded accessibility tree. Estimated severity ------------------ Medium - wrong or confusing presentation; the flow still completes. Estimated effort to fix ----------------------- Small - a few hours. The change itself is one switch in ErrorView.swift. The time goes into agreeing which codes count as expected outcomes, wording their titles, and adding those strings to Localizable.xcstrings for the six shipped languages. Filed by an agent (bot-cg) on behalf of Christian Grothoff, from an automated UI exploration campaign. Left unassigned deliberately. | ||||
| Tags | No tags attached. | ||||
| Attached Files | fix-internal-core-error.patch (3,337 bytes)
diff --git a/TalerWallet1/Views/Main/ErrorView.swift b/TalerWallet1/Views/Main/ErrorView.swift
index af392083..8b8e4d21 100644
--- a/TalerWallet1/Views/Main/ErrorView.swift
+++ b/TalerWallet1/Views/Main/ErrorView.swift
@@ -102,6 +102,31 @@ enum ErrorData {
}
struct ErrorView: View {
+
+ /// Title for wallet-core errors that are expected outcomes rather than
+ /// faults, keyed by `TalerErrorCode`.
+ ///
+ /// Returning nil means "no better title than the generic one", so an
+ /// unrecognised code still falls through to the existing behaviour.
+ static func titleForExpected(code: Int) -> String? {
+ switch code {
+ case 7054: // WALLET_KYC_LIMIT_EXCEEDED
+ return String(localized: "Limit exceeded",
+ comment: "error title: the payment service refused the amount")
+ case 7055: // WALLET_NO_SUITABLE_EXCHANGE
+ return String(localized: "No suitable payment service",
+ comment: "error title: no exchange can serve this request")
+ case 7064: // WALLET_CONTRACT_TERMS_UNSUPPORTED
+ return String(localized: "Transaction Error")
+ case 7000, // WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE
+ 7017: // WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE
+ return String(localized: "Incompatible version",
+ comment: "error title: the server speaks a protocol this app does not")
+ default:
+ return nil
+ }
+ }
+
let stack: CallStack?
var title: String
var exclam: Bool
@@ -152,6 +177,23 @@ struct ErrorView: View {
break
}
let hint = responseError.errorResponse?.hint ?? responseError.hint // nested one preferred
+ // Not every wallet-core error is an internal fault.
+ // Limits, quotas and similar conditions are ordinary
+ // outcomes the user can act on, and titling them
+ // "Internal core error" tells them they hit a bug when
+ // they did not. Give those a title that matches what
+ // happened; anything genuinely unexpected keeps the
+ // internal-error title.
+ let expectedTitle = ErrorView.titleForExpected(code: code)
+ if let expectedTitle {
+ self.init(stack, title: expectedTitle,
+ exclam: false,
+ message: hint,
+ requestUrl: responseError.requestUrl,
+ copyable: devMode,
+ onDismiss: onDismiss)
+ break
+ }
if !devMode, let hint {
// prefer hint for regular users
self.init(stack, title: walletCoreError, message: hint, copyable: true, onDismiss: onDismiss)
| ||||
| Put to | Asked by | Status | Note |
|---|---|---|---|
| MarcS | Christian Grothoff | waiting for an answer | 30,234 |
A question is put to one person and moves the issue nowhere: it can be assigned, worked on and implemented with a question open, because whoever asked is the one who knows whether the answer still matters. Several may be open at once. The question itself is the note, so it is formatted, notified and in the record like everything else anybody said here.
| Now reproducible on demand, which closes the verification gap noted when this was filed. The harness injects the code directly through wallet-core's api-error experiment, so the real condition no longer has to be provoked. From harness/ on a Mac with Xcode: python3 -m taler_fuzz build python3 -m taler_fuzz replay --tape "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" \ --label limit --fault core.err_limit --shots core.err_limit delivers taler://dev-experiment/api-error?operation=getBalances &detail=<base64url {"code":7054,"hint":"withdrawal would exceed a hard limit of the exchange"}>&count=3 to the running app. Observed in campaign episode camp4-e00018, fault applied 9.5 s into the walk; the "Payment Services" screen then rendered, verbatim from the recorded accessibility tree: Internal core error { "hint" : "withdrawal would exceed a hard limit of the exchange", "code" : 7054 } [ Copy the error JSON ] [ OK ] So the title is "Internal core error" even on a screen that has nothing to do with withdrawals -- the error is presented wherever the failing call happens to surface, here the payment-services list. The two protocol-version codes behave the same way under core.err_proto_exchange (7000) and core.err_proto_bank (7017): an error is shown, and nothing in it mentions a version or suggests updating. These four faults -- core.err_limit, core.err_no_exchange, core.err_proto_exchange, core.err_proto_bank, core.err_contract -- assert that an error of this class is not presented with an internal-fault title, so they also serve as the regression test for the attached patch. | |
| Patch verified on the running app, with screenshots of both sides. Earlier I could only say the patch compiled. It is now an A/B on one walk: the same 90-step tape, the same injected fault, two builds. Attached -------- before-internal-core-error.png the app without the patch after-incompatible-version.png the app with it Both are step 21 of the same replay. What the two runs show ---------------------- Without the patch: Internal core error { "code" : 7017, "hint" : "bank integration protocol version is not compatible" } With the patch, same code, same walk: Incompatible version bank integration protocol version is not compatible The harness's own oracle agrees independently: the run without the patch raises [high] fault-misdescribed: expected_error reported as "internal core error" and the run with it does not -- the only thing left is a low-severity note that wallet-core returned 7017, which is true and is not a complaint about the presentation. How to re-run it ---------------- The episode that recorded the defect in the first place is camp7-e00046; its tape is replayed directly. On branch dev/phoenix-bot/fuzzbang, under harness/: TAPE=$(python3 -c 'import json; d=json.load(open("$work_dir/out/camp7-e00046/episode.json")); print(",".join(str(v) for v in d["tape"]))') # without the patch git checkout dev/phoenix-bot/fuzzbang && python3 -m taler_fuzz build python3 -m taler_fuzz --max-steps 90 replay --tape "$TAPE" \ --fault core.err_proto_bank --shots --label title-before # with it git checkout dev/phoenix-bot/verify-errtitle && python3 -m taler_fuzz build python3 -m taler_fuzz --max-steps 90 replay --tape "$TAPE" \ --fault core.err_proto_bank --shots --label title-after `core.err_proto_bank` hands wallet-core a taler://dev-experiment/api-error URI that makes getBalances return 7017 three times. The error view appears at step 21 and stays for the rest of the walk; 71 of the recorded screens contain it, so it is not a timing fluke. A note on reaching this view at all ----------------------------------- Two earlier attempts injected the same class of error into acceptManualWithdrawal and never reached an error view: the walk confirmed the withdrawal and went to the wire-transfer screen as if nothing had happened. getBalances is the operation that reliably carries an injected error, because the app calls it constantly. If you are adding more of these, inject there. Added by an agent (bot-cg) on behalf of Christian Grothoff. | |
| Please add time estimate. | |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-04 01:06 | bot-cg | New Issue | |
| 2026-11-04 01:06 | bot-cg | File Added: fix-internal-core-error.patch | |
| 2026-11-04 02:59 | bot-cg | Note Added: 0030221 | |
| 2026-11-04 08:38 | bot-cg | File Added: before-internal-core-error.png | |
| 2026-11-04 08:38 | bot-cg | File Added: after-incompatible-version.png | |
| 2026-11-04 08:38 | bot-cg | Note Added: 0030226 | |
| 2026-11-04 09:56 | Christian Grothoff | Implementer | => MarcS |
| 2026-11-04 09:56 | Christian Grothoff | Reviewer | => Christian Grothoff |
| 2026-11-04 09:56 | Christian Grothoff | Release signed by | => MarcS |
| 2026-11-04 09:56 | Christian Grothoff | Deployed by | => MarcS |
| 2026-11-04 09:56 | Christian Grothoff | Communicator | CM_7 => |
| 2026-11-04 09:56 | Christian Grothoff | Product Version | => 1.6 |
| 2026-11-04 09:56 | Christian Grothoff | Target Version | => 1.7 |
| 2026-11-04 09:56 | Christian Grothoff | Description Updated | |
| 2026-11-04 09:56 | Christian Grothoff | Note Added: 0030234 | |
| 2026-11-04 13:01 | Christian Grothoff | Status | new => planning |