diff options
| author | Florian Dold <florian@dold.me> | 2023-06-20 11:40:06 +0200 | 
|---|---|---|
| committer | Florian Dold <florian@dold.me> | 2023-06-20 11:40:06 +0200 | 
| commit | 9c708251f92e6691ebba80fa8d129c6c04cec618 (patch) | |
| tree | edf46c7b3f9386697a4ea697c2d66f66323a6d3e /packages/taler-wallet-core/src/operations | |
| parent | 54f0c82999833132baf83995526025ac56d6fe06 (diff) | |
wallet-core: emit DD37 self-transition notifications with errors
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
17 files changed, 602 insertions, 271 deletions
| diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index cda5a012b..7f73a14b0 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -62,7 +62,7 @@ import { InternalWalletState } from "../../internal-wallet-state.js";  import { assertUnreachable } from "../../util/assertUnreachable.js";  import { checkLogicInvariant } from "../../util/invariants.js";  import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js"; -import { makeCoinAvailable, makeTombstoneId, TombstoneTag } from "../common.js"; +import { constructTombstone, makeCoinAvailable, TombstoneTag } from "../common.js";  import { getExchangeDetails } from "../exchanges.js";  import { extractContractData } from "../pay-merchant.js";  import { provideBackupState } from "./state.js"; @@ -472,7 +472,10 @@ export async function importBackup(        for (const backupWg of backupBlob.withdrawal_groups) {          const reservePub = cryptoComp.reservePrivToPub[backupWg.reserve_priv];          checkLogicInvariant(!!reservePub); -        const ts = makeTombstoneId(TombstoneTag.DeleteReserve, reservePub); +        const ts = constructTombstone({ +          tag: TombstoneTag.DeleteReserve, +          reservePub, +        });          if (tombstoneSet.has(ts)) {            continue;          } @@ -558,10 +561,10 @@ export async function importBackup(        }        for (const backupPurchase of backupBlob.purchases) { -        const ts = makeTombstoneId( -          TombstoneTag.DeletePayment, -          backupPurchase.proposal_id, -        ); +        const ts = constructTombstone({ +          tag: TombstoneTag.DeletePayment, +          proposalId: backupPurchase.proposal_id, +        });          if (tombstoneSet.has(ts)) {            continue;          } @@ -704,10 +707,10 @@ export async function importBackup(        }        for (const backupRefreshGroup of backupBlob.refresh_groups) { -        const ts = makeTombstoneId( -          TombstoneTag.DeleteRefreshGroup, -          backupRefreshGroup.refresh_group_id, -        ); +        const ts = constructTombstone({ +          tag: TombstoneTag.DeleteRefreshGroup, +          refreshGroupId: backupRefreshGroup.refresh_group_id, +        });          if (tombstoneSet.has(ts)) {            continue;          } @@ -800,10 +803,10 @@ export async function importBackup(        }        for (const backupTip of backupBlob.tips) { -        const ts = makeTombstoneId( -          TombstoneTag.DeleteTip, -          backupTip.wallet_tip_id, -        ); +        const ts = constructTombstone({ +          tag: TombstoneTag.DeleteTip, +          walletTipId: backupTip.wallet_tip_id, +        });          if (tombstoneSet.has(ts)) {            continue;          } diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index f726167da..364e876ec 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -29,52 +29,52 @@ import {    AmountString,    AttentionType,    BackupRecovery, +  Codec, +  DenomKeyType, +  EddsaKeyPair, +  HttpStatusCode, +  Logger, +  PreparePayResult, +  PreparePayResultType, +  RecoveryLoadRequest, +  RecoveryMergeStrategy, +  TalerError, +  TalerErrorCode, +  TalerErrorDetail, +  TalerPreciseTimestamp, +  URL, +  WalletBackupContentV1,    buildCodecForObject,    buildCodecForUnion,    bytesToString, -  canonicalizeBaseUrl,    canonicalJson, -  Codec, +  canonicalizeBaseUrl,    codecForAmountString,    codecForBoolean,    codecForConstString,    codecForList,    codecForNumber,    codecForString, -  codecForTalerErrorDetail,    codecOptional, -  ConfirmPayResultType,    decodeCrock, -  DenomKeyType,    durationFromSpec,    eddsaGetPublic, -  EddsaKeyPair,    encodeCrock,    getRandomBytes,    hash,    hashDenomPub, -  HttpStatusCode,    j2s,    kdf, -  Logger,    notEmpty, -  PaymentStatus, -  PreparePayResult, -  PreparePayResultType, -  RecoveryLoadRequest, -  RecoveryMergeStrategy, -  ReserveTransactionType,    rsaBlind,    secretbox,    secretbox_open,    stringToBytes, -  TalerErrorCode, -  TalerErrorDetail, -  TalerProtocolTimestamp, -  TalerPreciseTimestamp, -  URL, -  WalletBackupContentV1,  } from "@gnu-taler/taler-util"; +import { +  readSuccessResponseJsonOrThrow, +  readTalerErrorResponse, +} from "@gnu-taler/taler-util/http";  import { gunzipSync, gzipSync } from "fflate";  import { TalerCryptoInterface } from "../../crypto/cryptoImplementation.js";  import { @@ -86,29 +86,19 @@ import {    ConfigRecordKey,    WalletBackupConfState,  } from "../../db.js"; -import { TalerError } from "@gnu-taler/taler-util";  import { InternalWalletState } from "../../internal-wallet-state.js";  import { assertUnreachable } from "../../util/assertUnreachable.js";  import { -  readSuccessResponseJsonOrThrow, -  readTalerErrorResponse, -} from "@gnu-taler/taler-util/http"; -import {    checkDbInvariant,    checkLogicInvariant,  } from "../../util/invariants.js"; +import { addAttentionRequest, removeAttentionRequest } from "../attention.js";  import {    OperationAttemptResult,    OperationAttemptResultType,    TaskIdentifiers, -  scheduleRetryInTx, -} from "../../util/retries.js"; -import { addAttentionRequest, removeAttentionRequest } from "../attention.js"; -import { -  checkPaymentByProposalId, -  confirmPay, -  preparePayForUri, -} from "../pay-merchant.js"; +} from "../common.js"; +import { checkPaymentByProposalId, preparePayForUri } from "../pay-merchant.js";  import { exportBackup } from "./export.js";  import { BackupCryptoPrecomputedData, importBackup } from "./import.js";  import { getWalletBackupState, provideBackupState } from "./state.js"; @@ -380,8 +370,6 @@ async function runBackupCycleForProvider(              logger.warn("backup provider not found anymore");              return;            } -          const opId = TaskIdentifiers.forBackup(prov); -          await scheduleRetryInTx(ws, tx, opId);            prov.shouldRetryFreshProposal = true;            prov.state = {              tag: BackupProviderStateTag.Retrying, @@ -407,7 +395,7 @@ async function runBackupCycleForProvider(            return;          }          const opId = TaskIdentifiers.forBackup(prov); -        await scheduleRetryInTx(ws, tx, opId); +        //await scheduleRetryInTx(ws, tx, opId);          prov.currentPaymentProposalId = result.proposalId;          prov.shouldRetryFreshProposal = false;          prov.state = { @@ -481,7 +469,7 @@ async function runBackupCycleForProvider(          // FIXME: Allocate error code for this situation?          // FIXME: Add operation retry record!          const opId = TaskIdentifiers.forBackup(prov); -        await scheduleRetryInTx(ws, tx, opId); +        //await scheduleRetryInTx(ws, tx, opId);          prov.state = {            tag: BackupProviderStateTag.Retrying,          }; diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index ad18767c4..293870a18 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -18,42 +18,56 @@   * Imports.   */  import { +  AbsoluteTime,    AgeRestriction,    AmountJson,    Amounts,    CancellationToken,    CoinRefreshRequest,    CoinStatus, +  Duration, +  ErrorInfoSummary,    ExchangeEntryStatus,    ExchangeListItem,    ExchangeTosStatus,    getErrorDetailFromException,    j2s,    Logger, +  NotificationType,    OperationErrorInfo,    RefreshReason,    TalerErrorCode,    TalerErrorDetail,    TombstoneIdStr,    TransactionIdStr, +  TransactionType, +  WalletNotification,  } from "@gnu-taler/taler-util";  import {    WalletStoresV1,    CoinRecord,    ExchangeDetailsRecord,    ExchangeRecord, +  BackupProviderRecord, +  DepositGroupRecord, +  PeerPullPaymentIncomingRecord, +  PeerPullPaymentInitiationRecord, +  PeerPushPaymentIncomingRecord, +  PeerPushPaymentInitiationRecord, +  PurchaseRecord, +  RecoupGroupRecord, +  RefreshGroupRecord, +  TipRecord, +  WithdrawalGroupRecord,  } from "../db.js";  import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util";  import { InternalWalletState } from "../internal-wallet-state.js";  import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; -import { GetReadWriteAccess } from "../util/query.js"; -import { -  OperationAttemptResult, -  OperationAttemptResultType, -  RetryInfo, -} from "../util/retries.js"; +import { GetReadOnlyAccess, GetReadWriteAccess } from "../util/query.js";  import { CryptoApiStoppedError } from "../crypto/workers/crypto-dispatcher.js"; -import { TaskId } from "../pending-types.js"; +import { PendingTaskType, TaskId } from "../pending-types.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; +import { constructTransactionIdentifier } from "./transactions.js";  const logger = new Logger("operations/common.ts"); @@ -197,68 +211,185 @@ export async function spendCoins(    );  } -export async function storeOperationError( +/** + * Convert the task ID for a task that processes a transaction int + * the ID for the transaction. + */ +function convertTaskToTransactionId( +  taskId: string, +): TransactionIdStr | undefined { +  const parsedTaskId = parseTaskIdentifier(taskId); +  switch (parsedTaskId.tag) { +    case PendingTaskType.PeerPullCredit: +      return constructTransactionIdentifier({ +        tag: TransactionType.PeerPullCredit, +        pursePub: parsedTaskId.pursePub, +      }); +    case PendingTaskType.PeerPullDebit: +      return constructTransactionIdentifier({ +        tag: TransactionType.PeerPullDebit, +        peerPullPaymentIncomingId: parsedTaskId.peerPullPaymentIncomingId, +      }); +    // FIXME: This doesn't distinguish internal-withdrawal. +    // Maybe we should have a different task type for that as well? +    // Or maybe transaction IDs should be valid task identifiers? +    case PendingTaskType.Withdraw: +      return constructTransactionIdentifier({ +        tag: TransactionType.Withdrawal, +        withdrawalGroupId: parsedTaskId.withdrawalGroupId, +      }); +    case PendingTaskType.PeerPushCredit: +      return constructTransactionIdentifier({ +        tag: TransactionType.PeerPushCredit, +        peerPushPaymentIncomingId: parsedTaskId.peerPushPaymentIncomingId, +      }); +    case PendingTaskType.Deposit: +      return constructTransactionIdentifier({ +        tag: TransactionType.Deposit, +        depositGroupId: parsedTaskId.depositGroupId, +      }); +    case PendingTaskType.Refresh: +      return constructTransactionIdentifier({ +        tag: TransactionType.Refresh, +        refreshGroupId: parsedTaskId.refreshGroupId, +      }); +    case PendingTaskType.TipPickup: +      return constructTransactionIdentifier({ +        tag: TransactionType.Tip, +        walletTipId: parsedTaskId.walletTipId, +      }); +    case PendingTaskType.PeerPushDebit: +      return constructTransactionIdentifier({ +        tag: TransactionType.PeerPushDebit, +        pursePub: parsedTaskId.pursePub, +      }); +    case PendingTaskType.Purchase: +      return constructTransactionIdentifier({ +        tag: TransactionType.Payment, +        proposalId: parsedTaskId.proposalId, +      }); +    default: +      return undefined; +  } +} + +/** + * For tasks that process a transaction, + * generate a state transition notification. + */ +async function taskToTransactionNotification( +  ws: InternalWalletState, +  tx: GetReadOnlyAccess<typeof WalletStoresV1>, +  pendingTaskId: string, +  e: TalerErrorDetail | undefined, +): Promise<WalletNotification | undefined> { +  const txId = convertTaskToTransactionId(pendingTaskId); +  if (!txId) { +    return undefined; +  } +  const txState = await ws.getTransactionState(ws, tx, txId); +  if (!txState) { +    return undefined; +  } +  const notif: WalletNotification = { +    type: NotificationType.TransactionStateTransition, +    transactionId: txId, +    oldTxState: txState, +    newTxState: txState, +  }; +  if (e) { +    notif.errorInfo = { +      code: e.code as number, +      hint: e.hint, +    }; +  } +  return notif; +} + +async function storePendingTaskError(    ws: InternalWalletState,    pendingTaskId: string,    e: TalerErrorDetail,  ): Promise<void> { -  await ws.db -    .mktx((x) => [x.operationRetries]) -    .runReadWrite(async (tx) => { -      let retryRecord = await tx.operationRetries.get(pendingTaskId); -      if (!retryRecord) { -        retryRecord = { -          id: pendingTaskId, -          lastError: e, -          retryInfo: RetryInfo.reset(), -        }; -      } else { -        retryRecord.lastError = e; -        retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); -      } +  logger.info(`storing pending task error for ${pendingTaskId}`); +  const maybeNotification = await ws.db.mktxAll().runReadWrite(async (tx) => { +    let retryRecord = await tx.operationRetries.get(pendingTaskId); +    if (!retryRecord) { +      retryRecord = { +        id: pendingTaskId, +        lastError: e, +        retryInfo: RetryInfo.reset(), +      }; +    } else { +      retryRecord.lastError = e; +      retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); +    } +    await tx.operationRetries.put(retryRecord); +    return taskToTransactionNotification(ws, tx, pendingTaskId, e); +  }); +  if (maybeNotification) { +    ws.notify(maybeNotification); +  } +} + +export async function resetPendingTaskTimeout( +  ws: InternalWalletState, +  pendingTaskId: string, +): Promise<void> { +  const maybeNotification = await ws.db.mktxAll().runReadWrite(async (tx) => { +    let retryRecord = await tx.operationRetries.get(pendingTaskId); +    if (retryRecord) { +      // Note that we don't reset the lastError, it should still be visible +      // while the retry runs. +      retryRecord.retryInfo = RetryInfo.reset();        await tx.operationRetries.put(retryRecord); -    }); +    } +    return taskToTransactionNotification(ws, tx, pendingTaskId, undefined); +  }); +  if (maybeNotification) { +    ws.notify(maybeNotification); +  }  } -export async function resetOperationTimeout( +async function storePendingTaskPending(    ws: InternalWalletState,    pendingTaskId: string,  ): Promise<void> { -  await ws.db -    .mktx((x) => [x.operationRetries]) -    .runReadWrite(async (tx) => { -      let retryRecord = await tx.operationRetries.get(pendingTaskId); -      if (retryRecord) { -        // Note that we don't reset the lastError, it should still be visible -        // while the retry runs. -        retryRecord.retryInfo = RetryInfo.reset(); -        await tx.operationRetries.put(retryRecord); +  const maybeNotification = await ws.db.mktxAll().runReadWrite(async (tx) => { +    let retryRecord = await tx.operationRetries.get(pendingTaskId); +    let hadError = false; +    if (!retryRecord) { +      retryRecord = { +        id: pendingTaskId, +        retryInfo: RetryInfo.reset(), +      }; +    } else { +      if (retryRecord.lastError) { +        hadError = true;        } -    }); +      delete retryRecord.lastError; +      retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); +    } +    await tx.operationRetries.put(retryRecord); +    return taskToTransactionNotification(ws, tx, pendingTaskId, undefined); +  }); +  if (maybeNotification) { +    ws.notify(maybeNotification); +  }  } -export async function storeOperationPending( +async function storePendingTaskFinished(    ws: InternalWalletState,    pendingTaskId: string,  ): Promise<void> {    await ws.db      .mktx((x) => [x.operationRetries])      .runReadWrite(async (tx) => { -      let retryRecord = await tx.operationRetries.get(pendingTaskId); -      if (!retryRecord) { -        retryRecord = { -          id: pendingTaskId, -          retryInfo: RetryInfo.reset(), -        }; -      } else { -        delete retryRecord.lastError; -        retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); -      } -      await tx.operationRetries.put(retryRecord); +      await tx.operationRetries.delete(pendingTaskId);      });  } -export async function runOperationWithErrorReporting<T1, T2>( +export async function runTaskWithErrorReporting<T1, T2>(    ws: InternalWalletState,    opId: TaskId,    f: () => Promise<OperationAttemptResult<T1, T2>>, @@ -268,13 +399,13 @@ export async function runOperationWithErrorReporting<T1, T2>(      const resp = await f();      switch (resp.type) {        case OperationAttemptResultType.Error: -        await storeOperationError(ws, opId, resp.errorDetail); +        await storePendingTaskError(ws, opId, resp.errorDetail);          return resp;        case OperationAttemptResultType.Finished: -        await storeOperationFinished(ws, opId); +        await storePendingTaskFinished(ws, opId);          return resp;        case OperationAttemptResultType.Pending: -        await storeOperationPending(ws, opId); +        await storePendingTaskPending(ws, opId);          return resp;        case OperationAttemptResultType.Longpoll:          return resp; @@ -297,7 +428,7 @@ export async function runOperationWithErrorReporting<T1, T2>(        logger.warn("operation processed resulted in error");        logger.warn(`error was: ${j2s(e.errorDetail)}`);        maybeError = e.errorDetail; -      await storeOperationError(ws, opId, maybeError!); +      await storePendingTaskError(ws, opId, maybeError!);        return {          type: OperationAttemptResultType.Error,          errorDetail: e.errorDetail, @@ -315,7 +446,7 @@ export async function runOperationWithErrorReporting<T1, T2>(          },          `unexpected exception (message: ${e.message})`,        ); -      await storeOperationError(ws, opId, maybeError); +      await storePendingTaskError(ws, opId, maybeError);        return {          type: OperationAttemptResultType.Error,          errorDetail: maybeError, @@ -327,7 +458,7 @@ export async function runOperationWithErrorReporting<T1, T2>(          {},          `unexpected exception (not even an error)`,        ); -      await storeOperationError(ws, opId, maybeError); +      await storePendingTaskError(ws, opId, maybeError);        return {          type: OperationAttemptResultType.Error,          errorDetail: maybeError, @@ -336,17 +467,6 @@ export async function runOperationWithErrorReporting<T1, T2>(    }  } -export async function storeOperationFinished( -  ws: InternalWalletState, -  pendingTaskId: string, -): Promise<void> { -  await ws.db -    .mktx((x) => [x.operationRetries]) -    .runReadWrite(async (tx) => { -      await tx.operationRetries.delete(pendingTaskId); -    }); -} -  export enum TombstoneTag {    DeleteWithdrawalGroup = "delete-withdrawal-group",    DeleteReserve = "delete-reserve", @@ -361,15 +481,6 @@ export enum TombstoneTag {    DeletePeerPushCredit = "delete-peer-push-credit",  } -/** - * Create an event ID from the type and the primary key for the event. - * - * @deprecated use constructTombstone instead - */ -export function makeTombstoneId(type: TombstoneTag, ...args: string[]): string { -  return `tmb:${type}:${args.map((x) => encodeURIComponent(x)).join(":")}`; -} -  export function getExchangeTosStatus(    exchangeDetails: ExchangeDetailsRecord,  ): ExchangeTosStatus { @@ -432,7 +543,7 @@ export function runLongpollAsync(    const asyncFn = async () => {      if (ws.stopped) {        logger.trace("not long-polling reserve, wallet already stopped"); -      await storeOperationPending(ws, retryTag); +      await storePendingTaskPending(ws, retryTag);        return;      }      const cts = CancellationToken.create(); @@ -446,13 +557,13 @@ export function runLongpollAsync(        };        res = await reqFn(cts.token);      } catch (e) { -      await storeOperationError(ws, retryTag, getErrorDetailFromException(e)); +      await storePendingTaskError(ws, retryTag, getErrorDetailFromException(e));        return;      } finally {        delete ws.activeLongpoll[retryTag];      }      if (!res.ready) { -      await storeOperationPending(ws, retryTag); +      await storePendingTaskPending(ws, retryTag);      }      ws.workAvailable.trigger();    }; @@ -464,7 +575,11 @@ export type ParsedTombstone =        tag: TombstoneTag.DeleteWithdrawalGroup;        withdrawalGroupId: string;      } -  | { tag: TombstoneTag.DeleteRefund; refundGroupId: string }; +  | { tag: TombstoneTag.DeleteRefund; refundGroupId: string } +  | { tag: TombstoneTag.DeleteReserve; reservePub: string } +  | { tag: TombstoneTag.DeleteRefreshGroup; refreshGroupId: string } +  | { tag: TombstoneTag.DeleteTip; walletTipId: string } +  | { tag: TombstoneTag.DeletePayment; proposalId: string };  export function constructTombstone(p: ParsedTombstone): TombstoneIdStr {    switch (p.tag) { @@ -472,6 +587,16 @@ export function constructTombstone(p: ParsedTombstone): TombstoneIdStr {        return `tmb:${p.tag}:${p.withdrawalGroupId}` as TombstoneIdStr;      case TombstoneTag.DeleteRefund:        return `tmb:${p.tag}:${p.refundGroupId}` as TombstoneIdStr; +    case TombstoneTag.DeleteReserve: +      return `tmb:${p.tag}:${p.reservePub}` as TombstoneIdStr; +    case TombstoneTag.DeletePayment: +      return `tmb:${p.tag}:${p.proposalId}` as TombstoneIdStr; +    case TombstoneTag.DeleteRefreshGroup: +      return `tmb:${p.tag}:${p.refreshGroupId}` as TombstoneIdStr; +    case TombstoneTag.DeleteTip: +      return `tmb:${p.tag}:${p.walletTipId}` as TombstoneIdStr; +    default: +      assertUnreachable(p);    }  } @@ -487,3 +612,305 @@ export interface TransactionManager {    resume(): Promise<void>;    process(): Promise<OperationAttemptResult>;  } + +export enum OperationAttemptResultType { +  Finished = "finished", +  Pending = "pending", +  Error = "error", +  Longpoll = "longpoll", +} + +export type OperationAttemptResult<TSuccess = unknown, TPending = unknown> = +  | OperationAttemptFinishedResult<TSuccess> +  | OperationAttemptErrorResult +  | OperationAttemptLongpollResult +  | OperationAttemptPendingResult<TPending>; + +export namespace OperationAttemptResult { +  export function finishedEmpty(): OperationAttemptResult<unknown, unknown> { +    return { +      type: OperationAttemptResultType.Finished, +      result: undefined, +    }; +  } +  export function pendingEmpty(): OperationAttemptResult<unknown, unknown> { +    return { +      type: OperationAttemptResultType.Pending, +      result: undefined, +    }; +  } +  export function longpoll(): OperationAttemptResult<unknown, unknown> { +    return { +      type: OperationAttemptResultType.Longpoll, +    }; +  } +} + +export interface OperationAttemptFinishedResult<T> { +  type: OperationAttemptResultType.Finished; +  result: T; +} + +export interface OperationAttemptPendingResult<T> { +  type: OperationAttemptResultType.Pending; +  result: T; +} + +export interface OperationAttemptErrorResult { +  type: OperationAttemptResultType.Error; +  errorDetail: TalerErrorDetail; +} + +export interface OperationAttemptLongpollResult { +  type: OperationAttemptResultType.Longpoll; +} + +export interface RetryInfo { +  firstTry: AbsoluteTime; +  nextRetry: AbsoluteTime; +  retryCounter: number; +} + +export interface RetryPolicy { +  readonly backoffDelta: Duration; +  readonly backoffBase: number; +  readonly maxTimeout: Duration; +} + +const defaultRetryPolicy: RetryPolicy = { +  backoffBase: 1.5, +  backoffDelta: Duration.fromSpec({ seconds: 1 }), +  maxTimeout: Duration.fromSpec({ minutes: 2 }), +}; + +function updateTimeout( +  r: RetryInfo, +  p: RetryPolicy = defaultRetryPolicy, +): void { +  const now = AbsoluteTime.now(); +  if (now.t_ms === "never") { +    throw Error("assertion failed"); +  } +  if (p.backoffDelta.d_ms === "forever") { +    r.nextRetry = AbsoluteTime.never(); +    return; +  } + +  const nextIncrement = +    p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); + +  const t = +    now.t_ms + +    (p.maxTimeout.d_ms === "forever" +      ? nextIncrement +      : Math.min(p.maxTimeout.d_ms, nextIncrement)); +  r.nextRetry = AbsoluteTime.fromMilliseconds(t); +} + +export namespace RetryInfo { +  export function getDuration( +    r: RetryInfo | undefined, +    p: RetryPolicy = defaultRetryPolicy, +  ): Duration { +    if (!r) { +      // If we don't have any retry info, run immediately. +      return { d_ms: 0 }; +    } +    if (p.backoffDelta.d_ms === "forever") { +      return { d_ms: "forever" }; +    } +    const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); +    return { +      d_ms: +        p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t), +    }; +  } + +  export function reset(p: RetryPolicy = defaultRetryPolicy): RetryInfo { +    const now = AbsoluteTime.now(); +    const info = { +      firstTry: now, +      nextRetry: now, +      retryCounter: 0, +    }; +    updateTimeout(info, p); +    return info; +  } + +  export function increment( +    r: RetryInfo | undefined, +    p: RetryPolicy = defaultRetryPolicy, +  ): RetryInfo { +    if (!r) { +      return reset(p); +    } +    const r2 = { ...r }; +    r2.retryCounter++; +    updateTimeout(r2, p); +    return r2; +  } +} + +/** + * Parsed representation of task identifiers. + */ +export type ParsedTaskIdentifier = +  | { +      tag: PendingTaskType.Withdraw; +      withdrawalGroupId: string; +    } +  | { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string } +  | { tag: PendingTaskType.Backup; backupProviderBaseUrl: string } +  | { tag: PendingTaskType.Deposit; depositGroupId: string } +  | { tag: PendingTaskType.ExchangeCheckRefresh; exchangeBaseUrl: string } +  | { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string } +  | { tag: PendingTaskType.PeerPullDebit; peerPullPaymentIncomingId: string } +  | { tag: PendingTaskType.PeerPullCredit; pursePub: string } +  | { tag: PendingTaskType.PeerPushCredit; peerPushPaymentIncomingId: string } +  | { tag: PendingTaskType.PeerPushDebit; pursePub: string } +  | { tag: PendingTaskType.Purchase; proposalId: string } +  | { tag: PendingTaskType.Recoup; recoupGroupId: string } +  | { tag: PendingTaskType.TipPickup; walletTipId: string } +  | { tag: PendingTaskType.Refresh; refreshGroupId: string }; + +export function parseTaskIdentifier(x: string): ParsedTaskIdentifier { +  const task = x.split(":"); + +  if (task.length < 2) { +    throw Error("task id should have al least 2 parts separated by ':'"); +  } + +  const [type, ...rest] = task; +  switch (type) { +    case PendingTaskType.Backup: +      return { tag: type, backupProviderBaseUrl: rest[0] }; +    case PendingTaskType.Deposit: +      return { tag: type, depositGroupId: rest[0] }; +    case PendingTaskType.ExchangeCheckRefresh: +      return { tag: type, exchangeBaseUrl: rest[0] }; +    case PendingTaskType.ExchangeUpdate: +      return { tag: type, exchangeBaseUrl: rest[0] }; +    case PendingTaskType.PeerPullCredit: +      return { tag: type, pursePub: rest[0] }; +    case PendingTaskType.PeerPullDebit: +      return { tag: type, peerPullPaymentIncomingId: rest[0] }; +    case PendingTaskType.PeerPushCredit: +      return { tag: type, peerPushPaymentIncomingId: rest[0] }; +    case PendingTaskType.PeerPushDebit: +      return { tag: type, pursePub: rest[0] }; +    case PendingTaskType.Purchase: +      return { tag: type, proposalId: rest[0] }; +    case PendingTaskType.Recoup: +      return { tag: type, recoupGroupId: rest[0] }; +    case PendingTaskType.Refresh: +      return { tag: type, refreshGroupId: rest[0] }; +    case PendingTaskType.TipPickup: +      return { tag: type, walletTipId: rest[0] }; +    case PendingTaskType.Withdraw: +      return { tag: type, withdrawalGroupId: rest[0] }; +    default: +      throw Error("invalid task identifier"); +  } +} + +export function constructTaskIdentifier(p: ParsedTaskIdentifier): TaskId { +  switch (p.tag) { +    case PendingTaskType.Backup: +      return `${p.tag}:${p.backupProviderBaseUrl}` as TaskId; +    case PendingTaskType.Deposit: +      return `${p.tag}:${p.depositGroupId}` as TaskId; +    case PendingTaskType.ExchangeCheckRefresh: +      return `${p.tag}:${p.exchangeBaseUrl}` as TaskId; +    case PendingTaskType.ExchangeUpdate: +      return `${p.tag}:${p.exchangeBaseUrl}` as TaskId; +    case PendingTaskType.PeerPullDebit: +      return `${p.tag}:${p.peerPullPaymentIncomingId}` as TaskId; +    case PendingTaskType.PeerPushCredit: +      return `${p.tag}:${p.peerPushPaymentIncomingId}` as TaskId; +    case PendingTaskType.PeerPullCredit: +      return `${p.tag}:${p.pursePub}` as TaskId; +    case PendingTaskType.PeerPushDebit: +      return `${p.tag}:${p.pursePub}` as TaskId; +    case PendingTaskType.Purchase: +      return `${p.tag}:${p.proposalId}` as TaskId; +    case PendingTaskType.Recoup: +      return `${p.tag}:${p.recoupGroupId}` as TaskId; +    case PendingTaskType.Refresh: +      return `${p.tag}:${p.refreshGroupId}` as TaskId; +    case PendingTaskType.TipPickup: +      return `${p.tag}:${p.walletTipId}` as TaskId; +    case PendingTaskType.Withdraw: +      return `${p.tag}:${p.withdrawalGroupId}` as TaskId; +    default: +      assertUnreachable(p); +  } +} + +export namespace TaskIdentifiers { +  export function forWithdrawal(wg: WithdrawalGroupRecord): TaskId { +    return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}` as TaskId; +  } +  export function forExchangeUpdate(exch: ExchangeRecord): TaskId { +    return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}` as TaskId; +  } +  export function forExchangeUpdateFromUrl(exchBaseUrl: string): TaskId { +    return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}` as TaskId; +  } +  export function forExchangeCheckRefresh(exch: ExchangeRecord): TaskId { +    return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId; +  } +  export function forTipPickup(tipRecord: TipRecord): TaskId { +    return `${PendingTaskType.TipPickup}:${tipRecord.walletTipId}` as TaskId; +  } +  export function forRefresh(refreshGroupRecord: RefreshGroupRecord): TaskId { +    return `${PendingTaskType.Refresh}:${refreshGroupRecord.refreshGroupId}` as TaskId; +  } +  export function forPay(purchaseRecord: PurchaseRecord): TaskId { +    return `${PendingTaskType.Purchase}:${purchaseRecord.proposalId}` as TaskId; +  } +  export function forRecoup(recoupRecord: RecoupGroupRecord): TaskId { +    return `${PendingTaskType.Recoup}:${recoupRecord.recoupGroupId}` as TaskId; +  } +  export function forDeposit(depositRecord: DepositGroupRecord): TaskId { +    return `${PendingTaskType.Deposit}:${depositRecord.depositGroupId}` as TaskId; +  } +  export function forBackup(backupRecord: BackupProviderRecord): TaskId { +    return `${PendingTaskType.Backup}:${backupRecord.baseUrl}` as TaskId; +  } +  export function forPeerPushPaymentInitiation( +    ppi: PeerPushPaymentInitiationRecord, +  ): TaskId { +    return `${PendingTaskType.PeerPushDebit}:${ppi.pursePub}` as TaskId; +  } +  export function forPeerPullPaymentInitiation( +    ppi: PeerPullPaymentInitiationRecord, +  ): TaskId { +    return `${PendingTaskType.PeerPullCredit}:${ppi.pursePub}` as TaskId; +  } +  export function forPeerPullPaymentDebit( +    ppi: PeerPullPaymentIncomingRecord, +  ): TaskId { +    return `${PendingTaskType.PeerPullDebit}:${ppi.peerPullPaymentIncomingId}` as TaskId; +  } +  export function forPeerPushCredit( +    ppi: PeerPushPaymentIncomingRecord, +  ): TaskId { +    return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}` as TaskId; +  } +} + +/** + * Run an operation handler, expect a success result and extract the success value. + */ +export async function unwrapOperationHandlerResultOrThrow<T>( +  res: OperationAttemptResult<T>, +): Promise<T> { +  switch (res.type) { +    case OperationAttemptResultType.Finished: +      return res.result; +    case OperationAttemptResultType.Error: +      throw TalerError.fromUncheckedDetail(res.errorDetail); +    default: +      throw Error(`unexpected operation result (${res.type})`); +  } +} diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 64180a3ea..6781696cf 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -79,8 +79,7 @@ import {  } from "../index.js";  import { InternalWalletState } from "../internal-wallet-state.js";  import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; -import { OperationAttemptResult } from "../util/retries.js"; -import { spendCoins, TombstoneTag } from "./common.js"; +import { constructTaskIdentifier, OperationAttemptResult, spendCoins, TombstoneTag } from "./common.js";  import { getExchangeDetails } from "./exchanges.js";  import {    extractContractData, @@ -94,7 +93,6 @@ import {    parseTransactionIdentifier,    stopLongpolling,  } from "./transactions.js"; -import { constructTaskIdentifier } from "../util/retries.js";  import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";  /** diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 40ef22c6d..7e01071b4 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -74,14 +74,8 @@ import {    GetReadOnlyAccess,    GetReadWriteAccess,  } from "../util/query.js"; -import { -  OperationAttemptResult, -  OperationAttemptResultType, -  TaskIdentifiers, -  unwrapOperationHandlerResultOrThrow, -} from "../util/retries.js";  import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js"; -import { runOperationWithErrorReporting } from "./common.js"; +import { OperationAttemptResult, OperationAttemptResultType, runTaskWithErrorReporting, TaskIdentifiers, unwrapOperationHandlerResultOrThrow } from "./common.js";  const logger = new Logger("exchanges.ts"); @@ -559,7 +553,7 @@ export async function updateExchangeFromUrl(  }> {    const canonUrl = canonicalizeBaseUrl(baseUrl);    return unwrapOperationHandlerResultOrThrow( -    await runOperationWithErrorReporting( +    await runTaskWithErrorReporting(        ws,        TaskIdentifiers.forExchangeUpdateFromUrl(canonUrl),        () => updateExchangeFromUrlHandler(ws, canonUrl, options), diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index c3f288ff7..ad6552f06 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -67,7 +67,6 @@ import {    TalerErrorCode,    TalerErrorDetail,    TalerPreciseTimestamp, -  TalerProtocolTimestamp,    TalerProtocolViolationError,    TalerUriAction,    TransactionAction, @@ -116,12 +115,11 @@ import {    OperationAttemptResult,    OperationAttemptResultType,    RetryInfo, -  scheduleRetry,    TaskIdentifiers, -} from "../util/retries.js"; +} from "./common.js";  import {    runLongpollAsync, -  runOperationWithErrorReporting, +  runTaskWithErrorReporting,    spendCoins,  } from "./common.js";  import { @@ -1254,7 +1252,7 @@ export async function runPayForConfirmPay(      tag: PendingTaskType.Purchase,      proposalId,    }); -  const res = await runOperationWithErrorReporting(ws, taskId, async () => { +  const res = await runTaskWithErrorReporting(ws, taskId, async () => {      return await processPurchasePay(ws, proposalId, { forceNow: true });    });    logger.trace(`processPurchasePay response type ${res.type}`); @@ -1618,18 +1616,11 @@ export async function processPurchasePay(          // Do this in the background, as it might take some time          handleInsufficientFunds(ws, proposalId, err).catch(async (e) => {            console.log("handling insufficient funds failed"); - -          await scheduleRetry(ws, TaskIdentifiers.forPay(purchase), { -            code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, -            when: AbsoluteTime.now(), -            message: "unexpected exception", -            hint: "unexpected exception", -            details: { -              exception: e.toString(), -            }, -          }); +          console.log(`${e.toString()}`);          }); +        // FIXME: Should we really consider this to be pending? +          return {            type: OperationAttemptResultType.Pending,            result: undefined, @@ -1694,11 +1685,6 @@ export async function processPurchasePay(      await unblockBackup(ws, proposalId);    } -  ws.notify({ -    type: NotificationType.PayOperationSuccess, -    proposalId: purchase.proposalId, -  }); -    return OperationAttemptResult.finishedEmpty();  } diff --git a/packages/taler-wallet-core/src/operations/pay-peer-common.ts b/packages/taler-wallet-core/src/operations/pay-peer-common.ts index 4856fbe36..1bc2e8d49 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-common.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-common.ts @@ -52,11 +52,6 @@ import { InternalWalletState } from "../internal-wallet-state.js";  import { checkDbInvariant } from "../util/invariants.js";  import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";  import { getTotalRefreshCost } from "./refresh.js"; -import { -  OperationAttemptLongpollResult, -  OperationAttemptResult, -  OperationAttemptResultType, -} from "../util/retries.js";  const logger = new Logger("operations/peer-to-peer.ts"); diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index 48b81d6c2..5baba8cdc 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -66,12 +66,9 @@ import {    OperationAttemptResult,    OperationAttemptResultType,    constructTaskIdentifier, -} from "../util/retries.js"; -import {    LongpollResult, -  resetOperationTimeout,    runLongpollAsync, -  runOperationWithErrorReporting, +  runTaskWithErrorReporting,  } from "./common.js";  import {    codecForExchangePurseStatus, @@ -486,26 +483,6 @@ export async function processPeerPullCredit(    switch (pullIni.status) {      case PeerPullPaymentInitiationStatus.Done: { -      // We implement this case so that the "retry" action on a peer-pull-credit transaction -      // also retries the withdrawal task. - -      logger.warn( -        "peer pull payment initiation is already finished, retrying withdrawal", -      ); - -      const withdrawalGroupId = pullIni.withdrawalGroupId; - -      if (withdrawalGroupId) { -        const taskId = constructTaskIdentifier({ -          tag: PendingTaskType.Withdraw, -          withdrawalGroupId, -        }); -        stopLongpolling(ws, taskId); -        await resetOperationTimeout(ws, taskId); -        await runOperationWithErrorReporting(ws, taskId, () => -          processWithdrawalGroup(ws, withdrawalGroupId), -        ); -      }        return {          type: OperationAttemptResultType.Finished,          result: undefined, @@ -811,7 +788,7 @@ export async function initiatePeerPullPayment(      pursePub: pursePair.pub,    }); -  await runOperationWithErrorReporting(ws, taskId, async () => { +  await runTaskWithErrorReporting(ws, taskId, async () => {      return processPeerPullCredit(ws, pursePair.pub);    }); diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts index 0595a9e67..322d9ca71 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts @@ -59,13 +59,15 @@ import {    createRefreshGroup,  } from "../index.js";  import { assertUnreachable } from "../util/assertUnreachable.js"; +import { checkLogicInvariant } from "../util/invariants.js";  import {    OperationAttemptResult,    OperationAttemptResultType,    TaskIdentifiers,    constructTaskIdentifier, -} from "../util/retries.js"; -import { runOperationWithErrorReporting, spendCoins } from "./common.js"; +  runTaskWithErrorReporting, +  spendCoins, +} from "./common.js";  import {    PeerCoinRepair,    codecForExchangePurseStatus, @@ -78,7 +80,6 @@ import {    notifyTransition,    stopLongpolling,  } from "./transactions.js"; -import { checkLogicInvariant } from "../util/invariants.js";  const logger = new Logger("pay-peer-pull-debit.ts"); @@ -462,7 +463,7 @@ export async function confirmPeerPullDebit(        return pi;      }); -  await runOperationWithErrorReporting( +  await runTaskWithErrorReporting(      ws,      TaskIdentifiers.forPeerPullPaymentDebit(ppi),      async () => { diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index 9b563b37e..cf698d512 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -60,12 +60,7 @@ import {  } from "../index.js";  import { assertUnreachable } from "../util/assertUnreachable.js";  import { checkDbInvariant } from "../util/invariants.js"; -import { -  OperationAttemptResult, -  OperationAttemptResultType, -  constructTaskIdentifier, -} from "../util/retries.js"; -import { runLongpollAsync } from "./common.js"; +import { OperationAttemptResult, OperationAttemptResultType, constructTaskIdentifier, runLongpollAsync } from "./common.js";  import { updateExchangeFromUrl } from "./exchanges.js";  import {    codecForExchangePurseStatus, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index fc7e868dc..c4209eb51 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -42,40 +42,41 @@ import {    j2s,    stringifyTalerUri,  } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../internal-wallet-state.js"; -import { -  selectPeerCoins, -  getTotalPeerPaymentCost, -  codecForExchangePurseStatus, -  queryCoinInfosForSelection, -  PeerCoinRepair, -} from "./pay-peer-common.js";  import {    HttpResponse,    readSuccessResponseJsonOrThrow,    readTalerErrorResponse,  } from "@gnu-taler/taler-util/http"; +import { EncryptContractRequest } from "../crypto/cryptoTypes.js";  import {    PeerPushPaymentInitiationRecord,    PeerPushPaymentInitiationStatus,    RefreshOperationStatus,    createRefreshGroup,  } from "../index.js"; +import { InternalWalletState } from "../internal-wallet-state.js";  import { PendingTaskType } from "../pending-types.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; +import { checkLogicInvariant } from "../util/invariants.js";  import {    OperationAttemptResult,    OperationAttemptResultType,    constructTaskIdentifier, -} from "../util/retries.js"; -import { runLongpollAsync, spendCoins } from "./common.js"; +  runLongpollAsync, +  spendCoins, +} from "./common.js"; +import { +  PeerCoinRepair, +  codecForExchangePurseStatus, +  getTotalPeerPaymentCost, +  queryCoinInfosForSelection, +  selectPeerCoins, +} from "./pay-peer-common.js";  import {    constructTransactionIdentifier,    notifyTransition,    stopLongpolling,  } from "./transactions.js"; -import { assertUnreachable } from "../util/assertUnreachable.js"; -import { checkLogicInvariant } from "../util/invariants.js"; -import { EncryptContractRequest } from "../crypto/cryptoTypes.js";  const logger = new Logger("pay-peer-push-debit.ts"); @@ -162,10 +163,10 @@ async function handlePurseCreationConflict(          case PeerPushPaymentInitiationStatus.PendingCreatePurse:          case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: {            const sel = coinSelRes.result; -          myPpi.coinSel =  { +          myPpi.coinSel = {              coinPubs: sel.coins.map((x) => x.coinPub),              contributions: sel.coins.map((x) => x.contribution), -          } +          };            break;          }          default: diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index a6450e08f..e7e7ffcfc 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -43,8 +43,8 @@ import {  import { AbsoluteTime } from "@gnu-taler/taler-util";  import { InternalWalletState } from "../internal-wallet-state.js";  import { GetReadOnlyAccess } from "../util/query.js"; -import { TaskIdentifiers } from "../util/retries.js";  import { GlobalIDB } from "@gnu-taler/idb-bridge"; +import { TaskIdentifiers } from "./common.js";  function getPendingCommon(    ws: InternalWalletState, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index fcb7d6c98..71eb58ec9 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -53,12 +53,9 @@ import { InternalWalletState } from "../internal-wallet-state.js";  import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";  import { checkDbInvariant } from "../util/invariants.js";  import { GetReadWriteAccess } from "../util/query.js"; -import { -  OperationAttemptResult, -  unwrapOperationHandlerResultOrThrow, -} from "../util/retries.js";  import { createRefreshGroup, processRefreshGroup } from "./refresh.js";  import { internalCreateWithdrawalGroup } from "./withdraw.js"; +import { OperationAttemptResult } from "./common.js";  const logger = new Logger("operations/recoup.ts"); diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index c2cf13857..e573ddb44 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -84,18 +84,12 @@ import {  } from "@gnu-taler/taler-util/http";  import { checkDbInvariant } from "../util/invariants.js";  import { GetReadOnlyAccess, GetReadWriteAccess } from "../util/query.js"; -import { -  constructTaskIdentifier, -  OperationAttemptResult, -  OperationAttemptResultType, -} from "../util/retries.js"; -import { makeCoinAvailable } from "./common.js"; +import { constructTaskIdentifier, makeCoinAvailable, OperationAttemptResult, OperationAttemptResultType } from "./common.js";  import { updateExchangeFromUrl } from "./exchanges.js";  import { selectWithdrawalDenominations } from "../util/coinSelection.js";  import {    isWithdrawableDenom,    PendingTaskType, -  WalletConfig,  } from "../index.js";  import {    constructTransactionIdentifier, diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 1a565e02f..b43fd2e8a 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -57,12 +57,7 @@ import {    readSuccessResponseJsonOrThrow,  } from "@gnu-taler/taler-util/http";  import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; -import { -  constructTaskIdentifier, -  OperationAttemptResult, -  OperationAttemptResultType, -} from "../util/retries.js"; -import { makeCoinAvailable } from "./common.js"; +import { constructTaskIdentifier, makeCoinAvailable, OperationAttemptResult, OperationAttemptResultType } from "./common.js";  import { updateExchangeFromUrl } from "./exchanges.js";  import {    getCandidateWithdrawalDenoms, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index b6dc2e8bd..82b7cea64 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -69,8 +69,12 @@ import { InternalWalletState } from "../internal-wallet-state.js";  import { PendingTaskType } from "../pending-types.js";  import { assertUnreachable } from "../util/assertUnreachable.js";  import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; -import { constructTaskIdentifier, TaskIdentifiers } from "../util/retries.js"; -import { resetOperationTimeout, TombstoneTag } from "./common.js"; +import { +  constructTaskIdentifier, +  resetPendingTaskTimeout, +  TaskIdentifiers, +  TombstoneTag, +} from "./common.js";  import {    abortDepositGroup,    failDepositTransaction, @@ -1388,7 +1392,7 @@ export async function retryTransaction(          tag: PendingTaskType.PeerPullCredit,          pursePub: parsedTx.pursePub,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1397,7 +1401,7 @@ export async function retryTransaction(          tag: PendingTaskType.Deposit,          depositGroupId: parsedTx.depositGroupId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1408,7 +1412,7 @@ export async function retryTransaction(          tag: PendingTaskType.Withdraw,          withdrawalGroupId: parsedTx.withdrawalGroupId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1417,7 +1421,7 @@ export async function retryTransaction(          tag: PendingTaskType.Purchase,          proposalId: parsedTx.proposalId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1426,7 +1430,7 @@ export async function retryTransaction(          tag: PendingTaskType.TipPickup,          walletTipId: parsedTx.walletTipId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1435,7 +1439,7 @@ export async function retryTransaction(          tag: PendingTaskType.Refresh,          refreshGroupId: parsedTx.refreshGroupId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1444,7 +1448,7 @@ export async function retryTransaction(          tag: PendingTaskType.PeerPullDebit,          peerPullPaymentIncomingId: parsedTx.peerPullPaymentIncomingId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1453,7 +1457,7 @@ export async function retryTransaction(          tag: PendingTaskType.PeerPushCredit,          peerPushPaymentIncomingId: parsedTx.peerPushPaymentIncomingId,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } @@ -1462,7 +1466,7 @@ export async function retryTransaction(          tag: PendingTaskType.PeerPushDebit,          pursePub: parsedTx.pursePub,        }); -      await resetOperationTimeout(ws, taskId); +      await resetPendingTaskTimeout(ws, taskId);        stopLongpolling(ws, taskId);        break;      } diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 88389fd99..dd07bdebc 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -92,10 +92,13 @@ import {  } from "@gnu-taler/taler-util";  import { InternalWalletState } from "../internal-wallet-state.js";  import { +  OperationAttemptResult, +  OperationAttemptResultType, +  TaskIdentifiers, +  constructTaskIdentifier,    makeCoinAvailable,    makeExchangeListItem,    runLongpollAsync, -  runOperationWithErrorReporting,  } from "../operations/common.js";  import {    HttpRequestLibrary, @@ -115,12 +118,6 @@ import {    GetReadWriteAccess,  } from "../util/query.js";  import { -  OperationAttemptResult, -  OperationAttemptResultType, -  TaskIdentifiers, -  constructTaskIdentifier, -} from "../util/retries.js"; -import {    WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,    WALLET_EXCHANGE_PROTOCOL_VERSION,  } from "../versions.js"; @@ -1225,10 +1222,6 @@ async function queryReserve(        result.talerErrorResponse.code ===          TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN      ) { -      ws.notify({ -        type: NotificationType.ReserveNotYetFound, -        reservePub, -      });        return { ready: false };      } else {        throwUnexpectedRequestError(resp, result.talerErrorResponse); @@ -1258,12 +1251,6 @@ async function queryReserve(    notifyTransition(ws, transactionId, transitionResult); -  // FIXME: This notification is deprecated with DD37 -  ws.notify({ -    type: NotificationType.WithdrawalGroupReserveReady, -    transactionId, -  }); -    return { ready: true };  } @@ -2053,8 +2040,6 @@ async function registerReserveWithBank(      });    notifyTransition(ws, transactionId, transitionInfo); -  // FIXME: This notification is deprecated with DD37 -  ws.notify({ type: NotificationType.ReserveRegisteredWithBank });  }  interface BankStatusResult { @@ -2176,15 +2161,6 @@ async function processReserveBankStatus(          const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());          r.wgInfo.bankInfo.timestampBankConfirmed = now;          r.status = WithdrawalGroupStatus.PendingQueryingStatus; -        // FIXME: Notification is deprecated with DD37. -        const transactionId = constructTransactionIdentifier({ -          tag: TransactionType.Withdrawal, -          withdrawalGroupId: r.withdrawalGroupId, -        }); -        ws.notify({ -          type: NotificationType.WithdrawalGroupBankConfirmed, -          transactionId, -        });        } else {          logger.info("withdrawal: transfer not yet confirmed by bank");          r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url; | 
