View Issue Details

IDProjectCategoryView StatusLast Update
0011222Talerwallet-corepublic2026-03-11 14:13
Reporteravalos Assigned To 
PrioritynormalSeveritymajorReproducibilityhave not tried
Status newResolutionopen 
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
        }
    ]
}
TagsNo tags attached.

Activities

Christian Grothoff

2026-03-10 10: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 11: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 11: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 12:03

reporter   ~0028071

Last edited: 2026-03-10 12: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 13: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 13: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 10: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 14: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 Modified Username Field Change
2026-03-10 10:50 avalos New Issue
2026-03-10 10:57 Christian Grothoff Note Added: 0028066
2026-03-10 11:09 avalos Note Added: 0028067
2026-03-10 11:42 avalos Note Added: 0028070
2026-03-10 12:03 MarcS Note Added: 0028071
2026-03-10 12:10 MarcS Note Edited: 0028071
2026-03-10 13:01 avalos Note Added: 0028073
2026-03-10 13:10 avalos Note Added: 0028074
2026-03-11 10:32 avalos Note Added: 0028086
2026-03-11 14:13 avalos Note Added: 0028091
2026-03-11 14:13 avalos File Added: dependencies.png
2026-03-11 14:13 avalos File Added: dependencies.dot