taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit ad39479e953e9703ca609dccd95b9029ad29ddf4
parent ff97f1f6f251851f643b874edcbb07d4661aee7b
Author: Florian Dold <florian@dold.me>
Date:   Wed,  4 Jun 2025 00:49:21 +0200

wallet-core: improve deposit long-polling, try periodically even when kyc-check isn't satisfied

Diffstat:
Mpackages/taler-wallet-core/src/db.ts | 97++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mpackages/taler-wallet-core/src/deposits.ts | 36++++++++++++++++++++++++++++++++++++
2 files changed, 87 insertions(+), 46 deletions(-)

diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts @@ -1079,29 +1079,29 @@ export type SlateRecord = Omit<TokenRecord, "tokenIssueSig">; */ export type DbWalletCoinHistoryItem = | { - type: "withdraw"; - transactionId: TransactionIdStr; - } + type: "withdraw"; + transactionId: TransactionIdStr; + } | { - type: "spend"; - transactionId: TransactionIdStr; - amount: AmountString; - } + type: "spend"; + transactionId: TransactionIdStr; + amount: AmountString; + } | { - type: "refresh"; - transactionId: TransactionIdStr; - amount: AmountString; - } + type: "refresh"; + transactionId: TransactionIdStr; + amount: AmountString; + } | { - type: "recoup"; - transactionId: TransactionIdStr; - amount: AmountString; - } + type: "recoup"; + transactionId: TransactionIdStr; + amount: AmountString; + } | { - type: "refund"; - transactionId: TransactionIdStr; - amount: AmountString; - }; + type: "refund"; + transactionId: TransactionIdStr; + amount: AmountString; + }; /** * History event for a coin from the wallet's perspective. @@ -1573,9 +1573,9 @@ export enum ConfigRecordKey { */ export type ConfigRecord = | { - key: ConfigRecordKey.WalletBackupState; - value: WalletBackupConfState; - } + key: ConfigRecordKey.WalletBackupState; + value: WalletBackupConfState; + } | { key: ConfigRecordKey.CurrencyDefaultsApplied; value: boolean } | { key: ConfigRecordKey.TestLoopTx; value: number } | { key: ConfigRecordKey.LastInitInfo; value: DbProtocolTimestamp } @@ -1863,15 +1863,15 @@ export enum BackupProviderStateTag { export type BackupProviderState = | { - tag: BackupProviderStateTag.Provisional; - } + tag: BackupProviderStateTag.Provisional; + } | { - tag: BackupProviderStateTag.Ready; - nextBackupTimestamp: DbPreciseTimestamp; - } + tag: BackupProviderStateTag.Ready; + nextBackupTimestamp: DbPreciseTimestamp; + } | { - tag: BackupProviderStateTag.Retrying; - }; + tag: BackupProviderStateTag.Retrying; + }; export interface BackupProviderRecord { /** @@ -2036,6 +2036,11 @@ export interface DepositGroupRecord { timestampFinished: DbPreciseTimestamp | undefined; + /** + * When did the wallet last try a deposit request? + */ + timestampLastDepositAttempt: DbPreciseTimestamp | undefined; + operationStatus: DepositOperationStatus; statusPerCoin?: DepositElementStatus[]; @@ -2872,12 +2877,13 @@ export const WalletStoresV1 = { versionAdded: 17, }, ), - byPurchaseIdAndChoiceIndex: describeIndex("byPurchaseIdAndChoiceIndex", [ - "purchaseId", - "choiceIndex", - ], { - versionAdded: 17, - }), + byPurchaseIdAndChoiceIndex: describeIndex( + "byPurchaseIdAndChoiceIndex", + ["purchaseId", "choiceIndex"], + { + versionAdded: 17, + }, + ), }, ), slates: describeStore( @@ -2887,12 +2893,13 @@ export const WalletStoresV1 = { versionAdded: 16, }), { - byPurchaseIdAndChoiceIndex: describeIndex("byPurchaseIdAndChoiceIndex", [ - "purchaseId", - "choiceIndex", - ], { - versionAdded: 17, - }), + byPurchaseIdAndChoiceIndex: describeIndex( + "byPurchaseIdAndChoiceIndex", + ["purchaseId", "choiceIndex"], + { + versionAdded: 17, + }, + ), byPurchaseIdAndChoiceIndexAndOutputIndex: describeIndex( "byPurchaseIdAndChoiceIndexAndOutputIndex", ["purchaseId", "choiceIndex", "outputIndex"], @@ -3511,9 +3518,7 @@ export async function importDb(db: IDBDatabase, dumpJson: any): Promise<void> { export interface FixupDescription { name: string; - fn( - tx: WalletDbReadOnlyTransaction<WalletDbStoresArr> - ): Promise<void>; + fn(tx: WalletDbReadOnlyTransaction<WalletDbStoresArr>): Promise<void>; } /** @@ -3729,7 +3734,7 @@ export async function openStoredBackupsDatabase( idbFactory, TALER_WALLET_STORED_BACKUPS_DB_NAME, 1, - () => { }, + () => {}, onStoredBackupsDbUpgradeNeeded, ); @@ -3757,7 +3762,7 @@ export async function openTalerDatabase( idbFactory, TALER_WALLET_META_DB_NAME, 1, - () => { }, + () => {}, onMetaDbUpgradeNeeded, ); diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -1052,6 +1052,20 @@ async function processDepositGroupPendingKyc( const { depositGroupId } = depositGroup; const ctx = new DepositTransactionContext(wex, depositGroupId); + if ( + depositGroup.timestampLastDepositAttempt == null || + AbsoluteTime.isExpired( + AbsoluteTime.addDuration( + timestampAbsoluteFromDb(depositGroup.timestampLastDepositAttempt), + Duration.fromSpec({ minutes: 2 }), + ), + ) + ) { + logger.info( + `deposit group is in pending(kyc), but trying deposit anyway after two minutes since last attempt`, + ); + return processDepositGroupPendingDeposit(wex, depositGroup); + } const kycInfo = depositGroup.kycInfo; if (!kycInfo) { @@ -1067,6 +1081,7 @@ async function processDepositGroupPendingKyc( `kyc-check/${kycInfo.paytoHash}`, kycInfo.exchangeBaseUrl, ); + url.searchParams.set("lpt", "3"); logger.info(`kyc url ${url.href}`); const kycStatusRes = await cancelableLongPoll(wex, url, { headers: { @@ -1114,6 +1129,7 @@ async function processDepositGroupPendingKyc( codecForAccountKycStatus(), ); logger.info(`kyc still pending (HTTP 202): ${j2s(statusResp)}`); + return TaskRunResult.longpollReturnedPending(); } else { throwUnexpectedRequestError( kycStatusRes, @@ -1556,6 +1572,14 @@ async function processDepositGroupTrack( } } +/** + * Try to deposit coins with the exchange. + * + * May either be called directly when the deposit group is + * in the pending(deposit) state or indirectly when the deposit + * group is in a KYC state but wants to try deposit anyway (in case KYC + * is for another operation). + */ async function processDepositGroupPendingDeposit( wex: WalletExecutionContext, depositGroup: DepositGroupRecord, @@ -1677,6 +1701,17 @@ async function processDepositGroupPendingDeposit( } } + await wex.db.runReadWriteTx({ storeNames: ["depositGroups"] }, async (tx) => { + const dg = await tx.depositGroups.get(depositGroup.depositGroupId); + if (!dg) { + return; + } + dg.timestampLastDepositAttempt = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); + await tx.depositGroups.put(dg); + }); + // FIXME: Cache these! const depositPermissions = await generateDepositPermissions( wex, @@ -2297,6 +2332,7 @@ export async function createDepositGroup( payto_uri: req.depositPaytoUri, salt: wireSalt, }, + timestampLastDepositAttempt: undefined, operationStatus: DepositOperationStatus.PendingDeposit, infoPerExchange, };