aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-util/src/walletTypes.ts17
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts17
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts114
-rw-r--r--packages/taler-wallet-core/src/wallet.ts17
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": {