View Issue Details

IDProjectCategoryView StatusLast Update
0011222Talerwallet-corepublic2026-11-03 14:19
Reporteravalos Assigned ToResolution deadline
ImplementerFlorian Dold ReviewerChristian Grothoff  
Release signed byChristian Grothoff Deployed byvecirex  
VerifierFlorian Dold Communicator 
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)10Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeveritytweakReproducibilityhave not tried
Status implementingResolutionopen 
Target Version1.8 
Summary0011222: adding taler-ops.ch exchange takes 4-6 seconds!
DescriptionThis is bad for onboarding new users!

Here are the performance stats in a fresh Android wallet, running wallet-core 1.3.13 (c0a35f4), where the only performed action was adding the exchange.taler-ops.ch exchange as part of the new "Withdraw CHF" button.

{
    "http-fetch": [
        {
            "url": "https://exchange.taler-ops.ch/keys",
            "maxDurationMs": 2497,
            "count": 1
        },
        {
            "url": "https://exchange.taler-ops.ch/terms",
            "maxDurationMs": 180,
            "count": 1
        }
    ],
    "db-query": [
        {
            "name": "<unknown>",
            "location": "updateExchangeFromUrlHandler",
            "maxDurationMs": 3442,
            "count": 1
        },
        {
            "name": "<unknown>",
            "location": "processValidateDenoms",
            "maxDurationMs": 163,
            "count": 4
        },
        {
            "name": "<unknown>",
            "location": "processValidateDenoms/callOperationHandlerForTaskId",
            "maxDurationMs": 104,
            "count": 6
        },
        {
            "name": "<unknown>",
            "location": "checkState/genericWaitForState",
            "maxDurationMs": 97,
            "count": 3
        },
        {
            "name": "<unknown>",
            "location": "processTaskExchangeAutoRefresh/callOperationHandlerForTaskId",
            "maxDurationMs": 93,
            "count": 2
        },
        {
            "name": "<unknown>",
            "location": "updateWithdrawalDenomsForExchange/doExchangeAutoRefresh",
            "maxDurationMs": 90,
            "count": 1
        },
        {
            "name": "<unknown>",
            "location": "updateExchangeFromUrlHandler/callOperationHandlerForTaskId",
            "maxDurationMs": 89,
            "count": 3
        },
        {
            "name": "<unknown>",
            "location": "listExchanges/dispatchRequestInternal",
            "maxDurationMs": 72,
            "count": 2
        },
        {
            "name": "<unknown>",
            "location": "getBalances/dispatchRequestInternal",
            "maxDurationMs": 71,
            "count": 2
        },
        {
            "name": "<unknown>",
            "location": "handleSetWalletRunConfig/dispatchRequestInternal",
            "maxDurationMs": 58,
            "count": 1
        }
    ],
    "crypto": [
        {
            "operation": "isValidDenom",
            "maxDurationMs": 26,
            "count": 344
        },
        {
            "operation": "isValidGlobalFees",
            "maxDurationMs": 1,
            "count": 1
        }
    ],
    "wallet-request": [
        {
            "operation": "addExchange",
            "maxDurationMs": 6507,
            "count": 1
        },
        {
            "operation": "initWallet",
            "maxDurationMs": 121,
            "count": 1
        },
        {
            "operation": "getWithdrawalDetailsForAmount",
            "maxDurationMs": 103,
            "count": 1
        },
        {
            "operation": "listExchanges",
            "maxDurationMs": 75,
            "count": 2
        },
        {
            "operation": "getBalances",
            "maxDurationMs": 73,
            "count": 2
        },
        {
            "operation": "getExchangeEntryByUrl",
            "maxDurationMs": 61,
            "count": 1
        },
        {
            "operation": "getCurrencySpecification",
            "maxDurationMs": 32,
            "count": 4
        },
        {
            "operation": "hintNetworkAvailability",
            "maxDurationMs": 21,
            "count": 2
        },
        {
            "operation": "testingGetPerformanceStats",
            "maxDurationMs": 2,
            "count": 2
        }
    ],
    "wallet-task": [
        {
            "taskId": "exchange-update:https%3A%2F%2Fexchange.taler-ops.ch%2F",
            "maxDurationMs": 6720,
            "count": 2
        },
        {
            "taskId": "validate-denoms:",
            "maxDurationMs": 459,
            "count": 5
        },
        {
            "taskId": "exchange-auto-refresh:https%3A%2F%2Fexchange.demo.taler.net%2F",
            "maxDurationMs": 93,
            "count": 1
        },
        {
            "taskId": "exchange-update:https%3A%2F%2Fexchange.demo.taler.net%2F",
            "maxDurationMs": 90,
            "count": 1
        },
        {
            "taskId": "exchange-auto-refresh:https%3A%2F%2Fexchange.taler-ops.ch%2F",
            "maxDurationMs": 14,
            "count": 1
        }
    ]
}
Tagsperformance

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

Christian Grothoff

2026-03-10 09:57

manager   ~0028066

The HTTP fetch already takes a long time. I wonder if the client tries HTTP2/3 first and needs to fall back to HTTP1. We should enable HTTP2+3 on the server.

avalos

2026-03-10 10:09

developer   ~0028067

(I think I switched WiFi network, hence the x2 faster fetch time)

Enabling/disabling HTTP/2 in the client doesn't seem to make much difference:

HTTP/2 disabled:

{
    "url": "https://exchange.taler-ops.ch/keys",
    "maxDurationMs": 1010,
    "count": 1
}

HTTP/2 enabled:
 {
    "url": "https://exchange.taler-ops.ch/keys",
    "maxDurationMs": 1062,
    "count": 1
}

In both cases the addExchange request takes ~5 seconds.

avalos

2026-03-10 10:42

reporter   ~0028070

Also worth noting, isValidDenom was called 344 times.

Upper bound for the operation was 26ms, meaning: 344 * 26 = 8944ms.

MarcS

2026-03-10 11:03

reporter   ~0028071

Last edited: 2026-03-10 11:10

This is what I see on iOS (virgin wallet, I tapped on the "Withdraw CHF" button to open the withdraw-exchange talerURI):

[10] 12:07:07.871 � WalletCore.swift:157 WalletCore#1 handleResponse(_:_:) {"type":"response","operation":"testingGetPerformanceStats","id":11,"result":{"stats":{
"db-query":[
{"type":"db-query","name":"<unknown>","location":"updateExchangeFromUrlHandler","maxDurationMs":779,"count":1},
{"type":"db-query","name":"<unknown>","location":"processValidateDenoms","maxDurationMs":66,"count":1},
{"type":"db-query","name":"<unknown>","location":"startUpdateExchangeEntry","maxDurationMs":66,"count":3},
{"type":"db-query","name":"<unknown>","location":"checkState/genericWaitForState","maxDurationMs":30,"count":4},
{"type":"db-query","name":"<unknown>","location":"updateWithdrawalDenomsForExchange","maxDurationMs":24,"count":1},
{"type":"db-query","name":"<unknown>","location":"lookupExchangeByUri/dispatchRequestInternal","maxDurationMs":17,"count":4},
{"type":"db-query","name":"<unknown>","location":"updateWithdrawalDenomsForExchange/doExchangeAutoRefresh","maxDurationMs":11,"count":1},
{"type":"db-query","name":"<unknown>","location":"processValidateDenoms/callOperationHandlerForTaskId","maxDurationMs":9,"count":2},
{"type":"db-query","name":"<unknown>","location":"startUpdateExchangeEntry/fetchFreshExchange","maxDurationMs":8,"count":3},
{"type":"db-query","name":"<unknown>","location":"getBalances/dispatchRequestInternal","maxDurationMs":4,"count":1},
{"type":"db-query","name":"<unknown>","location":"doExchangeAutoRefresh","maxDurationMs":3,"count":1},
{"type":"db-query","name":"<unknown>","location":"handleGetCurrencySpecification/dispatchRequestInternal","maxDurationMs":2,"count":1},
{"type":"db-query","name":"<unknown>","location":"processTaskExchangeAutoRefresh/callOperationHandlerForTaskId","maxDurationMs":2,"count":1},
{"type":"db-query","name":"<unknown>","location":"fetchFreshExchange/handlePrepareWithdrawExchange","maxDurationMs":1,"count":1},
{"type":"db-query","name":"<unknown>","location":"updateExchangeFromUrlHandler/callOperationHandlerForTaskId","maxDurationMs":1,"count":2},
{"type":"db-query","name":"<unknown>","location":"getPreferredExchangeForCurrency/internalGetWithdrawalDetailsForAmount","maxDurationMs":1,"count":2},
{"type":"db-query","name":"<unknown>","location":"fetchFreshExchange/getExchangeWithdrawalInfo","maxDurationMs":0,"count":2},
{"type":"db-query","name":"<unknown>","location":"fetchAccount/fetchWithdrawalAccountInfo","maxDurationMs":0,"count":2}],
"wallet-task":[
{"type":"wallet-task","taskId":"exchange-update:https%3A%2F%2Fexchange.taler-ops.ch%2F","maxDurationMs":1557,"count":2},
{"type":"wallet-task","taskId":"validate-denoms:","maxDurationMs":105,"count":2},
{"type":"wallet-task","taskId":"exchange-auto-refresh:https%3A%2F%2Fexchange.taler-ops.ch%2F","maxDurationMs":2,"count":1}],
"wallet-request":[
{"type":"wallet-request","operation":"prepareWithdrawExchange","maxDurationMs":1524,"count":1},
{"type":"wallet-request","operation":"getWithdrawalDetailsForAmount","maxDurationMs":73,"count":2},
{"type":"wallet-request","operation":"getExchangeEntryByUrl","maxDurationMs":17,"count":4},
{"type":"wallet-request","operation":"getBalances","maxDurationMs":4,"count":1},
{"type":"wallet-request","operation":"getCurrencySpecification","maxDurationMs":2,"count":1},
{"type":"wallet-request","operation":"getDefaultExchanges","maxDurationMs":0,"count":1},
{"type":"wallet-request","operation":"testingGetPerformanceStats","maxDurationMs":0,"count":1}],
"http-fetch":[
{"type":"http-fetch","url":"https://exchange.taler-ops.ch/keys","maxDurationMs":636,"count":1},
{"type":"http-fetch","url":"https://exchange.taler-ops.ch/terms","maxDurationMs":42,"count":1}],
"crypto":[
{"type":"crypto","operation":"isValidDenom","maxDurationMs":3,"count":89},
{"type":"crypto","operation":"isValidGlobalFees","maxDurationMs":0,"count":1}]}}}

avalos

2026-03-10 12:01

reporter   ~0028073

{"type":"http-fetch","url":"https://exchange.taler-ops.ch/keys","maxDurationMs":636,"count":1}
{"type":"http-fetch","url":"https://exchange.taler-ops.ch/terms","maxDurationMs":42,"count":1}

Fetch is faster in iOS, but this could also be explained by network speed.

{"type":"crypto","operation":"isValidDenom","maxDurationMs":3,"count":89}

Crazy difference.

Upper bound for isValidDenom: 3 (iOS) vs 26 (Android), and it's called only 89 times (iOS) vs 344 (Android).

(Worth noting that Marc is using `withdraw-exchange` instead of `add-exchange`.)

avalos

2026-03-10 12:10

reporter   ~0028074

This is now for taler://withdraw-exchange/exchange.taler-ops.ch/

{
    "http-fetch": [
        {
            "url": "https://exchange.taler-ops.ch/keys",
            "maxDurationMs": 951,
            "count": 1
        },
        {
            "url": "https://exchange.taler-ops.ch/terms",
            "maxDurationMs": 218,
            "count": 1
        }
    ],
    "db-query": [
        {
            "name": "<unknown>",
            "location": "updateExchangeFromUrlHandler",
            "maxDurationMs": 3042,
            "count": 1
        },
        {
            "name": "<unknown>",
            "location": "processValidateDenoms",
            "maxDurationMs": 158,
            "count": 5
        },
        {
            "name": "<unknown>",
            "location": "getBalances/dispatchRequestInternal",
            "maxDurationMs": 95,
            "count": 2
        },
        {
            "name": "<unknown>",
            "location": "checkState/genericWaitForState",
            "maxDurationMs": 90,
            "count": 3
        },
        {
            "name": "<unknown>",
            "location": "updateWithdrawalDenomsForExchange/doExchangeAutoRefresh",
            "maxDurationMs": 81,
            "count": 1
        },
        {
            "name": "<unknown>",
            "location": "processValidateDenoms/callOperationHandlerForTaskId",
            "maxDurationMs": 71,
            "count": 6
        },
        {
            "name": "<unknown>",
            "location": "processTaskExchangeAutoRefresh/callOperationHandlerForTaskId",
            "maxDurationMs": 70,
            "count": 2
        },
        {
            "name": "<unknown>",
            "location": "updateExchangeFromUrlHandler/callOperationHandlerForTaskId",
            "maxDurationMs": 67,
            "count": 3
        },
        {
            "name": "<unknown>",
            "location": "startUpdateExchangeEntry",
            "maxDurationMs": 42,
            "count": 2
        },
        {
            "name": "<unknown>",
            "location": "handleSetWalletRunConfig/dispatchRequestInternal",
            "maxDurationMs": 39,
            "count": 1
        }
    ],
    "crypto": [
        {
            "operation": "isValidDenom",
            "maxDurationMs": 34,
            "count": 369
        },
        {
            "operation": "isValidGlobalFees",
            "maxDurationMs": 1,
            "count": 1
        }
    ],
    "wallet-request": [
        {
            "operation": "prepareWithdrawExchange",
            "maxDurationMs": 4675,
            "count": 1
        },
        {
            "operation": "getWithdrawalDetailsForAmount",
            "maxDurationMs": 100,
            "count": 1
        },
        {
            "operation": "getBalances",
            "maxDurationMs": 99,
            "count": 2
        },
        {
            "operation": "initWallet",
            "maxDurationMs": 87,
            "count": 1
        },
        {
            "operation": "getCurrencySpecification",
            "maxDurationMs": 33,
            "count": 2
        },
        {
            "operation": "hintNetworkAvailability",
            "maxDurationMs": 32,
            "count": 2
        },
        {
            "operation": "getExchangeEntryByUrl",
            "maxDurationMs": 19,
            "count": 1
        },
        {
            "operation": "listExchanges",
            "maxDurationMs": 14,
            "count": 1
        },
        {
            "operation": "testingGetPerformanceStats",
            "maxDurationMs": 3,
            "count": 2
        }
    ],
    "wallet-task": [
        {
            "taskId": "exchange-update:https%3A%2F%2Fexchange.taler-ops.ch%2F",
            "maxDurationMs": 4769,
            "count": 2
        },
        {
            "taskId": "validate-denoms:",
            "maxDurationMs": 530,
            "count": 6
        },
        {
            "taskId": "exchange-auto-refresh:https%3A%2F%2Fexchange.demo.taler.net%2F",
            "maxDurationMs": 71,
            "count": 1
        },
        {
            "taskId": "exchange-update:https%3A%2F%2Fexchange.demo.taler.net%2F",
            "maxDurationMs": 68,
            "count": 1
        },
        {
            "taskId": "exchange-auto-refresh:https%3A%2F%2Fexchange.taler-ops.ch%2F",
            "maxDurationMs": 17,
            "count": 1
        }
    ]
}

avalos

2026-03-11 09:32

reporter   ~0028086

What is weird is the crazy amount of times that isValidDenom is being called.

I did my best to map the dependency call in wallet-core that leads to this call (graph attached), and found lots of redundant calls.

avalos

2026-03-11 13:13

reporter   ~0028091

Updated dependency graphs.
dependencies.png (509,327 bytes)
dependencies.dot (5,088 bytes)   
digraph CallGraph {
  rankdir=LR;
  node [shape=box];

  subgraph cluster_SHEPHERD {
    label="SHEPHERD";
    style=rounded;
    "getActiveTaskIds";
  }

  subgraph cluster_WALLET_API {
    label="WALLET API";
    style=rounded;
    "handlePrepareWithdrawExchange";
    "handleUpdateExchangeEntry";
  }

  subgraph cluster_WITHDRAWAL {
    label="WITHDRAWAL";
    style=rounded;
    "getWithdrawalDetailsForBankInfo";
    "updateWithdrawalDenomsForCurrency";
    "updateWithdrawalDenomsForExchange";
    "redenominateWithdrawal";
    "processQueryReserve";
    "getExchangeWithdrawalInfo";
    "getWithdrawalCandidateDenoms";
    "getInitialDenomsSelection";
    "processWithdrawalGroup";
    "processWithdrawalGroupRedenominate";
    "processWithdrawalGroupPendingReady";
    "internalPrepareCreateWithdrawalGroup";
    "confirmWithdrawal";
    "createManualWithdrawal";
  }

  subgraph cluster_P2P_PAYMENTS {
    label="P2P PAYMENTS";
    style=rounded;
    "getTotalPeerPaymentCost";
    "initiatePeerPullPayment";
    "preparePeerPushCredit";
    "confirmPeerPushCredit";
    "checkPeerPushDebitV2";
    "internalCheckPeerPushDebit";
  }

  subgraph cluster_REFRESH {
    label="REFRESH";
    style=rounded;
    "processRefreshGroup";
    "redenominateRefresh";
    "refreshMelt";
  }

  subgraph cluster_EXCHANGES {
    label="EXCHANGES";
    style=rounded;
    "doExchangeAutoRefresh";
    "listExchanges";
    "fetchFreshExchange";
    "waitReadyExchange";
  }

  subgraph cluster_DEPOSITS {
    label="DEPOSITS";
    style=rounded;
    "checkDepositGroup";
    "internalCheckDepositGroup";
    "createDepositGroup";
  }

  subgraph cluster_TASKS {
    label="TASKS";
    style=rounded;
    "PendingTaskType.ExchangeUpdate (run)";
    "PendingTaskType.ValidateDenoms (run)";
    "updateExchangeFromUrlHandler";
    "processValidateDenoms";
    "PendingTaskType.ExchangeAutoRefresh (run)";
    "processTaskExchangeAutoRefresh";
  }

  /* SHEPHERD edges */
  "getActiveTaskIds" -> "PendingTaskType.ValidateDenoms (run)";
  "getActiveTaskIds" -> "PendingTaskType.ExchangeUpdate (run)";

  /* WALLET API edges */
  "handlePrepareWithdrawExchange" -> "fetchFreshExchange";
  "handleUpdateExchangeEntry" -> "fetchFreshExchange";

  /* WITHDRAWAL edges */
  "getWithdrawalDetailsForBankInfo" -> "listExchanges";
  "getWithdrawalDetailsForBankInfo" -> "fetchFreshExchange";

  "updateWithdrawalDenomsForCurrency" -> "updateWithdrawalDenomsForExchange";
  "redenominateWithdrawal" -> "updateWithdrawalDenomsForExchange";

  "processQueryReserve" -> "fetchFreshExchange";
  "processQueryReserve" -> "updateWithdrawalDenomsForExchange";

  "getExchangeWithdrawalInfo" -> "fetchFreshExchange";
  "getExchangeWithdrawalInfo" -> "updateWithdrawalDenomsForExchange";

  "getWithdrawalCandidateDenoms" -> "updateWithdrawalDenomsForExchange";
  "getInitialDenomsSelection" -> "updateWithdrawalDenomsForExchange";

  "updateWithdrawalDenomsForExchange" -> "isValidDenomRecord";

  "processWithdrawalGroup" -> "processWithdrawalGroupRedenominate";
  "processWithdrawalGroup" -> "processWithdrawalGroupPendingReady";

  "processWithdrawalGroupRedenominate" -> "fetchFreshExchange";
  "processWithdrawalGroupRedenominate" -> "redenominateWithdrawal";

  "processWithdrawalGroupPendingReady" -> "fetchFreshExchange";

  "internalPrepareCreateWithdrawalGroup" -> "fetchFreshExchange";

  "confirmWithdrawal" -> "fetchFreshExchange";
  "createManualWithdrawal" -> "fetchFreshExchange";

  /* P2P PAYMENTS edges */
  "getTotalPeerPaymentCost" -> "updateWithdrawalDenomsForExchange";

  "initiatePeerPullPayment" -> "fetchFreshExchange";
  "preparePeerPushCredit" -> "fetchFreshExchange";
  "confirmPeerPushCredit" -> "fetchFreshExchange";

  "checkPeerPushDebitV2" -> "internalCheckPeerPushDebit";
  "internalCheckPeerPushDebit" -> "fetchFreshExchange";

  /* REFRESH edges */
  "processRefreshGroup" -> "updateWithdrawalDenomsForExchange";

  "redenominateRefresh" -> "fetchFreshExchange";
  "redenominateRefresh" -> "updateWithdrawalDenomsForExchange";

  "refreshMelt" -> "fetchFreshExchange";

  /* EXCHANGES edges */
  "doExchangeAutoRefresh" -> "updateWithdrawalDenomsForExchange";
  "listExchanges" -> "PendingTaskType.ExchangeUpdate (run)";
  "fetchFreshExchange" -> "waitReadyExchange";
  "waitReadyExchange" -> "PendingTaskType.ExchangeUpdate (run)";

  /* DEPOSITS edges */
  "checkDepositGroup" -> "internalCheckDepositGroup";
  "internalCheckDepositGroup" -> "fetchFreshExchange";
  "createDepositGroup" -> "fetchFreshExchange";

  /* TASKS edges */
  "PendingTaskType.ExchangeUpdate (run)" -> "updateExchangeFromUrlHandler";
  "updateExchangeFromUrlHandler" -> "PendingTaskType.ValidateDenoms (run)";
  "updateExchangeFromUrlHandler" -> "doExchangeAutoRefresh";

  "PendingTaskType.ValidateDenoms (run)" -> "processValidateDenoms";
  "processValidateDenoms" -> "isValidDenomRecord";

  "PendingTaskType.ExchangeAutoRefresh (run)" -> "processTaskExchangeAutoRefresh";
  "processTaskExchangeAutoRefresh" -> "fetchFreshExchange";
  "processTaskExchangeAutoRefresh" -> "doExchangeAutoRefresh";
}
dependencies.dot (5,088 bytes)   

Issue History

Date ModifiedUsernameFieldChange
2026-03-10 09:50avalosNew Issue
2026-03-10 09:57Christian GrothoffNote Added: 0028066
2026-03-10 10:09avalosNote Added: 0028067
2026-03-10 10:42avalosNote Added: 0028070
2026-03-10 11:03MarcSNote Added: 0028071
2026-03-10 11:10MarcSNote Edited: 0028071
2026-03-10 12:01avalosNote Added: 0028073
2026-03-10 12:10avalosNote Added: 0028074
2026-03-11 09:32avalosNote Added: 0028086
2026-03-11 13:13avalosNote Added: 0028091
2026-03-11 13:13avalosFile Added: dependencies.png
2026-03-11 13:13avalosFile Added: dependencies.dot
2026-03-17 19:44Christian GrothoffSeveritymajor => tweak
2026-03-17 19:44Christian GrothoffStatusnew => confirmed
2026-03-17 19:44Christian GrothoffTarget Version => 1.7
2026-08-09 22:50Christian GrothoffTarget Version1.7 => 1.8
2026-08-17 16:19Christian GrothoffTag Attached: performance
2026-11-03 14:19Christian GrothoffImplementer => Florian Dold
2026-11-03 14:19Christian GrothoffReviewer => Christian Grothoff
2026-11-03 14:19Christian GrothoffRelease signed by => Christian Grothoff
2026-11-03 14:19Christian GrothoffDeployed by => vecirex
2026-11-03 14:19Christian GrothoffVerifier => Florian Dold
2026-11-03 14:19Christian GrothoffEstimated effort (hours) => 10