diff options
| author | Florian Dold <florian@dold.me> | 2022-03-29 21:21:57 +0200 | 
|---|---|---|
| committer | Florian Dold <florian@dold.me> | 2022-03-29 21:22:03 +0200 | 
| commit | bbd6ccf1c7c0baea44234863967e640f5cb10a3a (patch) | |
| tree | 219086b57e229b79e674619180200284762c3f4b /packages | |
| parent | fdd272af203d7048f980a2f2b5d405e5c94ebec2 (diff) | |
wallet: allow forced denom selection for tests
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/taler-util/src/walletTypes.ts | 17 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/refresh.ts | 5 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/reserves.ts | 17 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/tip.ts | 3 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 114 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 17 | 
6 files changed, 105 insertions, 68 deletions
| diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 552087fb8..818ba37fe 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -212,6 +212,12 @@ export interface CreateReserveRequest {     * URL to fetch the withdraw status from the bank.     */    bankWithdrawStatusUrl?: string; + +  /** +   * Forced denomination selection for the first withdrawal +   * from this reserve, only used for testing. +   */ +  forcedDenomSel?: ForcedDenomSel;  }  export const codecForCreateReserveRequest = (): Codec<CreateReserveRequest> => @@ -727,6 +733,7 @@ export interface GetWithdrawalDetailsForAmountRequest {  export interface AcceptBankIntegratedWithdrawalRequest {    talerWithdrawUri: string;    exchangeBaseUrl: string; +  forcedDenomSel?: ForcedDenomSel;  }  export const codecForAcceptBankIntegratedWithdrawalRequest = @@ -734,6 +741,7 @@ export const codecForAcceptBankIntegratedWithdrawalRequest =      buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()        .property("exchangeBaseUrl", codecForString())        .property("talerWithdrawUri", codecForString()) +      .property("forcedDenomSel", codecForAny())        .build("AcceptBankIntegratedWithdrawalRequest");  export const codecForGetWithdrawalDetailsForAmountRequest = @@ -1134,6 +1142,9 @@ export const codecForImportDbRequest = (): Codec<ImportDb> =>      .property("dump", codecForAny())      .build("ImportDbRequest"); - - -    
\ No newline at end of file +export interface ForcedDenomSel { +  denoms: { +    value: AmountString; +    count: number; +  }[]; +} diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 762023d2e..cf292061f 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -106,11 +106,12 @@ export function getTotalRefreshCost(      amountLeft,      refreshedDenom.feeRefresh,    ).amount; +  const denomMap = Object.fromEntries(denoms.map((x) => [x.denomPubHash, x]));    const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms);    const resultingAmount = Amounts.add(      Amounts.getZero(withdrawAmount.currency),      ...withdrawDenoms.selectedDenoms.map( -      (d) => Amounts.mult(d.denom.value, d.count).amount, +      (d) => Amounts.mult(denomMap[d.denomPubHash].value, d.count).amount,      ),    ).amount;    const totalCost = Amounts.sub(amountLeft, resultingAmount).amount; @@ -277,7 +278,7 @@ async function refreshCreateSession(          sessionSecretSeed: sessionSecretSeed,          newDenoms: newCoinDenoms.selectedDenoms.map((x) => ({            count: x.count, -          denomPubHash: x.denom.denomPubHash, +          denomPubHash: x.denomPubHash,          })),          amountRefreshOutput: newCoinDenoms.totalCoinValue,        }; diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index 38a7386b2..91c19fbf0 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -37,6 +37,8 @@ import {    TalerErrorDetail,    AbsoluteTime,    URL, +  AmountString, +  ForcedDenomSel,  } from "@gnu-taler/taler-util";  import { InternalWalletState } from "../internal-wallet-state.js";  import { @@ -68,7 +70,6 @@ import {    updateExchangeFromUrl,  } from "./exchanges.js";  import { -  denomSelectionInfoToState,    getBankWithdrawalInfo,    getCandidateWithdrawalDenoms,    processWithdrawGroup, @@ -180,8 +181,7 @@ export async function createReserve(    await updateWithdrawalDenoms(ws, canonExchange);    const denoms = await getCandidateWithdrawalDenoms(ws, canonExchange); -  const denomSelInfo = selectWithdrawalDenominations(req.amount, denoms); -  const initialDenomSel = denomSelectionInfoToState(denomSelInfo); +  const initialDenomSel = selectWithdrawalDenominations(req.amount, denoms);    const reserveRecord: ReserveRecord = {      instructedAmount: req.amount, @@ -630,7 +630,7 @@ async function updateReserve(          amountReservePlus,          amountReserveMinus,        ).amount; -      const denomSelInfo = selectWithdrawalDenominations( +      const denomSel = selectWithdrawalDenominations(          remainingAmount,          denoms,        ); @@ -639,11 +639,11 @@ async function updateReserve(          `Remaining unclaimed amount in reseve is ${Amounts.stringify(            remainingAmount,          )} and can be withdrawn with ${ -          denomSelInfo.selectedDenoms.length +          denomSel.selectedDenoms.length          } coins`,        ); -      if (denomSelInfo.selectedDenoms.length === 0) { +      if (denomSel.selectedDenoms.length === 0) {          newReserve.reserveStatus = ReserveRecordStatus.Dormant;          newReserve.operationStatus = OperationStatus.Finished;          delete newReserve.lastError; @@ -669,7 +669,7 @@ async function updateReserve(          timestampStart: AbsoluteTime.toTimestamp(AbsoluteTime.now()),          retryInfo: resetRetryInfo(),          lastError: undefined, -        denomsSel: denomSelectionInfoToState(denomSelInfo), +        denomsSel: denomSel,          secretSeed: encodeCrock(getRandomBytes(64)),          denomSelUid: encodeCrock(getRandomBytes(32)),          operationStatus: OperationStatus.Pending, @@ -755,6 +755,9 @@ export async function createTalerWithdrawReserve(    ws: InternalWalletState,    talerWithdrawUri: string,    selectedExchange: string, +  options: { +    forcedDenomSel?: ForcedDenomSel; +  } = {},  ): Promise<AcceptWithdrawalResponse> {    await updateExchangeFromUrl(ws, selectedExchange);    const withdrawInfo = await getBankWithdrawalInfo(ws.http, talerWithdrawUri); diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 8bf85fe99..c0dcae911 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -56,7 +56,6 @@ import {    updateWithdrawalDenoms,    getCandidateWithdrawalDenoms,    selectWithdrawalDenominations, -  denomSelectionInfoToState,  } from "./withdraw.js";  import {    getHttpResponseErrorDetails, @@ -133,7 +132,7 @@ export async function prepareTip(        tipAmountEffective: selectedDenoms.totalCoinValue,        retryInfo: resetRetryInfo(),        lastError: undefined, -      denomsSel: denomSelectionInfoToState(selectedDenoms), +      denomsSel: selectedDenoms,        pickedUpTimestamp: undefined,        secretSeed,        denomSelUid, diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index e7dcd0784..6d45599dc 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -31,6 +31,7 @@ import {    durationFromSpec,    ExchangeListItem,    ExchangeWithdrawRequest, +  ForcedDenomSel,    LibtoolVersion,    Logger,    NotificationType, @@ -68,6 +69,7 @@ import {    HttpRequestLibrary,    readSuccessResponseJsonOrThrow,  } from "../util/http.js"; +import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";  import {    resetRetryInfo,    RetryInfo, @@ -85,21 +87,6 @@ import { guardOperationException } from "./common.js";  const logger = new Logger("operations/withdraw.ts");  /** - * FIXME: Eliminate this in favor of DenomSelectionState. - */ -interface DenominationSelectionInfo { -  totalCoinValue: AmountJson; -  totalWithdrawCost: AmountJson; -  selectedDenoms: { -    /** -     * How many times do we withdraw this denomination? -     */ -    count: number; -    denom: DenominationRecord; -  }[]; -} - -/**   * Information about what will happen when creating a reserve.   *   * Sent to the wallet frontend to be rendered and shown to the user. @@ -122,7 +109,7 @@ export interface ExchangeWithdrawDetails {    /**     * Selected denominations for withdraw.     */ -  selectedDenoms: DenominationSelectionInfo; +  selectedDenoms: DenomSelectionState;    /**     * Does the wallet know about an auditor for @@ -213,12 +200,12 @@ export function isWithdrawableDenom(d: DenominationRecord): boolean {  export function selectWithdrawalDenominations(    amountAvailable: AmountJson,    denoms: DenominationRecord[], -): DenominationSelectionInfo { +): DenomSelectionState {    let remaining = Amounts.copy(amountAvailable);    const selectedDenoms: {      count: number; -    denom: DenominationRecord; +    denomPubHash: string;    }[] = [];    let totalCoinValue = Amounts.getZero(amountAvailable.currency); @@ -248,7 +235,7 @@ export function selectWithdrawalDenominations(        ).amount;        selectedDenoms.push({          count, -        denom: d, +        denomPubHash: d.denomPubHash,        });      } @@ -262,9 +249,7 @@ export function selectWithdrawalDenominations(        `selected withdrawal denoms for ${Amounts.stringify(totalCoinValue)}`,      );      for (const sd of selectedDenoms) { -      logger.trace( -        `denom_pub_hash=${sd.denom.denomPubHash}, count=${sd.count}`, -      ); +      logger.trace(`denom_pub_hash=${sd.denomPubHash}, count=${sd.count}`);      }      logger.trace("(end of withdrawal denom list)");    } @@ -276,6 +261,56 @@ export function selectWithdrawalDenominations(    };  } +export function selectForcedWithdrawalDenominations( +  amountAvailable: AmountJson, +  denoms: DenominationRecord[], +  forcedDenomSel: ForcedDenomSel, +): DenomSelectionState { +  let remaining = Amounts.copy(amountAvailable); + +  const selectedDenoms: { +    count: number; +    denomPubHash: string; +  }[] = []; + +  let totalCoinValue = Amounts.getZero(amountAvailable.currency); +  let totalWithdrawCost = Amounts.getZero(amountAvailable.currency); + +  denoms = denoms.filter(isWithdrawableDenom); +  denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); + +  for (const fds of forcedDenomSel.denoms) { +    const count = fds.count; +    const denom = denoms.find((x) => { +      return Amounts.cmp(x.value, fds.value) == 0; +    }); +    if (!denom) { +      throw Error( +        `unable to find denom for forced selection (value ${fds.value})`, +      ); +    } +    const cost = Amounts.add(denom.value, denom.feeWithdraw).amount; +    totalCoinValue = Amounts.add( +      totalCoinValue, +      Amounts.mult(denom.value, count).amount, +    ).amount; +    totalWithdrawCost = Amounts.add( +      totalWithdrawCost, +      Amounts.mult(cost, count).amount, +    ).amount; +    selectedDenoms.push({ +      count, +      denomPubHash: denom.denomPubHash, +    }); +  } + +  return { +    selectedDenoms, +    totalCoinValue, +    totalWithdrawCost, +  }; +} +  /**   * Get information about a withdrawal from   * a taler://withdraw URI by asking the bank. @@ -695,21 +730,6 @@ async function processPlanchetVerifyAndStoreCoin(    }  } -export function denomSelectionInfoToState( -  dsi: DenominationSelectionInfo, -): DenomSelectionState { -  return { -    selectedDenoms: dsi.selectedDenoms.map((x) => { -      return { -        count: x.count, -        denomPubHash: x.denom.denomPubHash, -      }; -    }), -    totalCoinValue: dsi.totalCoinValue, -    totalWithdrawCost: dsi.totalWithdrawCost, -  }; -} -  /**   * Make sure that denominations that currently can be used for withdrawal   * are validated, and the result of validation is stored in the database. @@ -1006,11 +1026,21 @@ export async function getExchangeWithdrawalInfo(      exchange,    ); -  let earliestDepositExpiration = -    selectedDenoms.selectedDenoms[0].denom.stampExpireDeposit; +  let earliestDepositExpiration: TalerProtocolTimestamp | undefined;    for (let i = 1; i < selectedDenoms.selectedDenoms.length; i++) { -    const expireDeposit = -      selectedDenoms.selectedDenoms[i].denom.stampExpireDeposit; +    const ds = selectedDenoms.selectedDenoms[i]; +    // FIXME: Do in one transaction! +    const denom = await ws.db +      .mktx((x) => ({ denominations: x.denominations })) +      .runReadOnly(async (tx) => { +        return ws.getDenomInfo(ws, tx, exchangeBaseUrl, ds.denomPubHash); +      }); +    checkDbInvariant(!!denom); +    const expireDeposit = denom.stampExpireDeposit; +    if (!earliestDepositExpiration) { +      earliestDepositExpiration = expireDeposit; +      continue; +    }      if (        AbsoluteTime.cmp(          AbsoluteTime.fromTimestamp(expireDeposit), @@ -1021,6 +1051,8 @@ export async function getExchangeWithdrawalInfo(      }    } +  checkLogicInvariant(!!earliestDepositExpiration); +    const possibleDenoms = await ws.db      .mktx((x) => ({ denominations: x.denominations }))      .runReadOnly(async (tx) => { diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index b0bd2a2cb..673a86167 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -598,18 +598,6 @@ async function getExchanges(    return { exchanges };  } -async function acceptWithdrawal( -  ws: InternalWalletState, -  talerWithdrawUri: string, -  selectedExchange: string, -): Promise<AcceptWithdrawalResponse> { -  try { -    return createTalerWithdrawReserve(ws, talerWithdrawUri, selectedExchange); -  } finally { -    ws.latch.trigger(); -  } -} -  /**   * Inform the wallet that the status of a reserve has changed (e.g. due to a   * confirmation from the bank.). @@ -849,10 +837,13 @@ async function dispatchRequestInternal(      case "acceptBankIntegratedWithdrawal": {        const req =          codecForAcceptBankIntegratedWithdrawalRequest().decode(payload); -      return await acceptWithdrawal( +      return await createTalerWithdrawReserve(          ws,          req.talerWithdrawUri,          req.exchangeBaseUrl, +        { +          forcedDenomSel: req.forcedDenomSel, +        },        );      }      case "getExchangeTos": { | 
