summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2020-12-16 17:59:04 +0100
committerFlorian Dold <florian@dold.me>2020-12-16 17:59:04 +0100
commitbafb52edff4d56bcb9e3c3d0a260f507c517b08c (patch)
tree09b484a8cedc9893f5ea7593a98fadde075eafa3 /packages/taler-wallet-core/src/operations
parentc09c5bbe625566fc61c811160d2ccdab263327fa (diff)
downloadwallet-core-bafb52edff4d56bcb9e3c3d0a260f507c517b08c.tar.gz
wallet-core-bafb52edff4d56bcb9e3c3d0a260f507c517b08c.tar.bz2
wallet-core-bafb52edff4d56bcb9e3c3d0a260f507c517b08c.zip
don't store reserve history anymore, adjust withdrawal implementation accordingly
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
-rw-r--r--packages/taler-wallet-core/src/operations/backup.ts41
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts1
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts7
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts367
-rw-r--r--packages/taler-wallet-core/src/operations/state.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts16
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw-test.ts4
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts67
11 files changed, 205 insertions, 307 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup.ts b/packages/taler-wallet-core/src/operations/backup.ts
index 7ab908c46..1e5aa542d 100644
--- a/packages/taler-wallet-core/src/operations/backup.ts
+++ b/packages/taler-wallet-core/src/operations/backup.ts
@@ -44,6 +44,7 @@ import {
BackupRefundState,
BackupReserve,
BackupTip,
+ BackupWithdrawalGroup,
WalletBackupContentV1,
} from "../types/backupTypes";
import { TransactionHandle } from "../util/query";
@@ -172,6 +173,7 @@ export async function exportBackup(
Stores.tips,
Stores.recoupGroups,
Stores.reserves,
+ Stores.withdrawalGroups,
],
async (tx) => {
const bs = await getWalletBackupState(ws, tx);
@@ -188,9 +190,46 @@ export async function exportBackup(
const backupBackupProviders: BackupBackupProvider[] = [];
const backupTips: BackupTip[] = [];
const backupRecoupGroups: BackupRecoupGroup[] = [];
+ const withdrawalGroupsByReserve: {
+ [reservePub: string]: BackupWithdrawalGroup[];
+ } = {};
+
+ await tx.iter(Stores.withdrawalGroups).forEachAsync(async (wg) => {
+ const withdrawalGroups = (withdrawalGroupsByReserve[
+ wg.reservePub
+ ] ??= []);
+ // FIXME: finish!
+ // withdrawalGroups.push({
+ // raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount),
+ // selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({
+ // count: x.count,
+ // denom_pub_hash: x.denomPubHash,
+ // })),
+ // timestamp_start: wg.timestampStart,
+ // timestamp_finish: wg.timestampFinish,
+ // withdrawal_group_id: wg.withdrawalGroupId,
+ // });
+ });
await tx.iter(Stores.reserves).forEach((reserve) => {
- // FIXME: implement
+ const backupReserve: BackupReserve = {
+ initial_selected_denoms: reserve.initialDenomSel.selectedDenoms.map(
+ (x) => ({
+ count: x.count,
+ denom_pub_hash: x.denomPubHash,
+ }),
+ ),
+ initial_withdrawal_group_id: reserve.initialWithdrawalGroupId,
+ instructed_amount: Amounts.stringify(reserve.instructedAmount),
+ reserve_priv: reserve.reservePriv,
+ timestamp_created: reserve.timestampCreated,
+ withdrawal_groups:
+ withdrawalGroupsByReserve[reserve.reservePub] ?? [],
+ };
+ const backupReserves = (backupReservesByExchange[
+ reserve.exchangeBaseUrl
+ ] ??= []);
+ backupReserves.push(backupReserve);
});
await tx.iter(Stores.tips).forEach((tip) => {
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index b6865cccc..3e71634cd 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -58,7 +58,6 @@ import {
} from "../util/http";
import { Logger } from "../util/logging";
import { URL } from "../util/url";
-import { reconcileReserveHistory } from "../util/reserveHistoryUtil";
import { checkDbInvariant } from "../util/invariants";
import { NotificationType } from "../types/notifications";
import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries";
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index 52f0c4510..c374cfe4a 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -460,6 +460,8 @@ async function recordConfirmPay(
paymentSubmitPending: true,
refunds: {},
merchantPaySig: undefined,
+ noncePriv: proposal.noncePriv,
+ noncePub: proposal.noncePub,
};
await ws.db.runWithWriteTransaction(
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts
index a42d89c9a..cc693a49d 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -29,7 +29,7 @@ import {
PendingOperationType,
ExchangeUpdateOperationStage,
ReserveType,
-} from "../types/pending";
+} from "../types/pendingTypes";
import {
Duration,
getTimestampNow,
@@ -189,7 +189,6 @@ async function gatherReservePending(
// nothing to report as pending
break;
case ReserveRecordStatus.WAIT_CONFIRM_BANK:
- case ReserveRecordStatus.WITHDRAWING:
case ReserveRecordStatus.QUERYING_STATUS:
case ReserveRecordStatus.REGISTERING_BANK:
resp.nextRetryDelay = updateRetryDelay(
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 71cc78fa9..2d80f0a50 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -29,7 +29,7 @@ import { amountToPretty } from "../util/helpers";
import { TransactionHandle } from "../util/query";
import { InternalWalletState, EXCHANGE_COINS_LOCK } from "./state";
import { Logger } from "../util/logging";
-import { getWithdrawDenomList, isWithdrawableDenom } from "./withdraw";
+import { selectWithdrawalDenominations, isWithdrawableDenom } from "./withdraw";
import { updateExchangeFromUrl } from "./exchanges";
import {
TalerErrorDetails,
@@ -83,7 +83,7 @@ export function getTotalRefreshCost(
): AmountJson {
const withdrawAmount = Amounts.sub(amountLeft, refreshedDenom.feeRefresh)
.amount;
- const withdrawDenoms = getWithdrawDenomList(withdrawAmount, denoms);
+ const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms);
const resultingAmount = Amounts.add(
Amounts.getZero(withdrawAmount.currency),
...withdrawDenoms.selectedDenoms.map(
@@ -150,7 +150,7 @@ async function refreshCreateSession(
oldDenom.feeRefresh,
).amount;
- const newCoinDenoms = getWithdrawDenomList(availableAmount, availableDenoms);
+ const newCoinDenoms = selectWithdrawalDenominations(availableAmount, availableDenoms);
if (newCoinDenoms.selectedDenoms.length === 0) {
logger.trace(
@@ -478,6 +478,7 @@ async function refreshReveal(
oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
},
suspended: false,
+ coinEvHash: pc.coinEv,
};
coins.push(coin);
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts
index a2a1b3018..95c38120c 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -28,8 +28,6 @@ import {
CurrencyRecord,
Stores,
WithdrawalGroupRecord,
- WalletReserveHistoryItemType,
- ReserveHistoryRecord,
ReserveBankInfo,
} from "../types/dbTypes";
import { Logger } from "../util/logging";
@@ -47,10 +45,12 @@ import { assertUnreachable } from "../util/assertUnreachable";
import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
import { randomBytes } from "../crypto/primitives/nacl-fast";
import {
- selectWithdrawalDenoms,
processWithdrawGroup,
getBankWithdrawalInfo,
denomSelectionInfoToState,
+ updateWithdrawalDenoms,
+ selectWithdrawalDenominations,
+ getPossibleWithdrawalDenoms,
} from "./withdraw";
import {
guardOperationException,
@@ -66,11 +66,6 @@ import {
durationMin,
durationMax,
} from "../util/time";
-import {
- reconcileReserveHistory,
- summarizeReserveHistory,
- ReserveHistorySummary,
-} from "../util/reserveHistoryUtil";
import { TransactionHandle } from "../util/query";
import { addPaytoQueryParams } from "../util/payto";
import { TalerErrorCode } from "../TalerErrorCode";
@@ -86,6 +81,7 @@ import {
getRetryDuration,
updateRetryInfoTimeout,
} from "../util/retries";
+import { ReserveTransactionType } from "../types/ReserveTransaction";
const logger = new Logger("reserves.ts");
@@ -138,11 +134,9 @@ export async function createReserve(
const initialWithdrawalGroupId = encodeCrock(getRandomBytes(32));
- const denomSelInfo = await selectWithdrawalDenoms(
- ws,
- canonExchange,
- req.amount,
- );
+ await updateWithdrawalDenoms(ws, canonExchange);
+ const denoms = await getPossibleWithdrawalDenoms(ws, canonExchange);
+ const denomSelInfo = selectWithdrawalDenominations(req.amount, denoms);
const initialDenomSel = denomSelectionInfoToState(denomSelInfo);
const reserveRecord: ReserveRecord = {
@@ -166,16 +160,6 @@ export async function createReserve(
requestedQuery: false,
};
- const reserveHistoryRecord: ReserveHistoryRecord = {
- reservePub: keypair.pub,
- reserveTransactions: [],
- };
-
- reserveHistoryRecord.reserveTransactions.push({
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: req.amount,
- });
-
const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange);
const exchangeDetails = exchangeInfo.details;
if (!exchangeDetails) {
@@ -206,12 +190,7 @@ export async function createReserve(
const cr: CurrencyRecord = currencyRecord;
const resp = await ws.db.runWithWriteTransaction(
- [
- Stores.currencies,
- Stores.reserves,
- Stores.reserveHistory,
- Stores.bankWithdrawUris,
- ],
+ [Stores.currencies, Stores.reserves, Stores.bankWithdrawUris],
async (tx) => {
// Check if we have already created a reserve for that bankWithdrawStatusUrl
if (reserveRecord.bankInfo?.statusUrl) {
@@ -238,7 +217,6 @@ export async function createReserve(
}
await tx.put(Stores.currencies, cr);
await tx.put(Stores.reserves, reserveRecord);
- await tx.put(Stores.reserveHistory, reserveHistoryRecord);
const r: CreateReserveResponse = {
exchange: canonExchange,
reservePub: keypair.pub,
@@ -499,6 +477,10 @@ async function incrementReserveRetry(
/**
* Update the information about a reserve that is stored in the wallet
* by quering the reserve's exchange.
+ *
+ * If the reserve have funds that are not allocated in a withdrawal group yet
+ * and are big enough to withdraw with available denominations,
+ * create a new withdrawal group for the remaining amount.
*/
async function updateReserve(
ws: InternalWalletState,
@@ -542,78 +524,130 @@ async function updateReserve(
}
const reserveInfo = result.response;
-
const balance = Amounts.parseOrThrow(reserveInfo.balance);
const currency = balance.currency;
- let updateSummary: ReserveHistorySummary | undefined;
- await ws.db.runWithWriteTransaction(
- [Stores.reserves, Stores.reserveHistory],
+
+ await updateWithdrawalDenoms(ws, reserve.exchangeBaseUrl);
+ const denoms = await getPossibleWithdrawalDenoms(ws, reserve.exchangeBaseUrl);
+
+ const newWithdrawalGroup = await ws.db.runWithWriteTransaction(
+ [Stores.coins, Stores.planchets, Stores.withdrawalGroups, Stores.reserves],
async (tx) => {
- const r = await tx.get(Stores.reserves, reservePub);
- if (!r) {
- return;
- }
- if (r.reserveStatus !== ReserveRecordStatus.QUERYING_STATUS) {
+ const newReserve = await tx.get(Stores.reserves, reserve.reservePub);
+ if (!newReserve) {
return;
}
+ let amountReservePlus = Amounts.getZero(currency);
+ let amountReserveMinus = Amounts.getZero(currency);
+
+ // Subtract withdrawal groups for this reserve from the available amount.
+ await tx
+ .iterIndexed(Stores.withdrawalGroups.byReservePub, reservePub)
+ .forEach((wg) => {
+ const cost = wg.denomsSel.totalWithdrawCost;
+ amountReserveMinus = Amounts.add(amountReserveMinus, cost).amount;
+ });
- const hist = await tx.get(Stores.reserveHistory, reservePub);
- if (!hist) {
- throw Error("inconsistent database");
+ for (const entry of reserveInfo.history) {
+ switch (entry.type) {
+ case ReserveTransactionType.Credit:
+ amountReservePlus = Amounts.add(
+ amountReservePlus,
+ Amounts.parseOrThrow(entry.amount),
+ ).amount;
+ break;
+ case ReserveTransactionType.Recoup:
+ amountReservePlus = Amounts.add(
+ amountReservePlus,
+ Amounts.parseOrThrow(entry.amount),
+ ).amount;
+ break;
+ case ReserveTransactionType.Closing:
+ amountReserveMinus = Amounts.add(
+ amountReserveMinus,
+ Amounts.parseOrThrow(entry.amount),
+ ).amount;
+ break;
+ case ReserveTransactionType.Withdraw: {
+ // Now we check if the withdrawal transaction
+ // is part of any withdrawal known to this wallet.
+ const planchet = await tx.getIndexed(
+ Stores.planchets.coinEvHashIndex,
+ entry.h_coin_envelope,
+ );
+ if (planchet) {
+ // Amount is already accounted in some withdrawal session
+ break;
+ }
+ const coin = await tx.getIndexed(
+ Stores.coins.coinEvHashIndex,
+ entry.h_coin_envelope,
+ );
+ if (coin) {
+ // Amount is already accounted in some withdrawal session
+ break;
+ }
+ // Amount has been claimed by some withdrawal we don't know about
+ amountReserveMinus = Amounts.add(
+ amountReserveMinus,
+ Amounts.parseOrThrow(entry.amount),
+ ).amount;
+ break;
+ }
+ }
}
- const newHistoryTransactions = reserveInfo.history.slice(
- hist.reserveTransactions.length,
+ const remainingAmount = Amounts.sub(amountReservePlus, amountReserveMinus)
+ .amount;
+ const denomSelInfo = selectWithdrawalDenominations(
+ remainingAmount,
+ denoms,
);
- const reserveUpdateId = encodeCrock(getRandomBytes(32));
+ if (denomSelInfo.selectedDenoms.length > 0) {
+ let withdrawalGroupId: string;
- const reconciled = reconcileReserveHistory(
- hist.reserveTransactions,
- reserveInfo.history,
- );
-
- updateSummary = summarizeReserveHistory(
- reconciled.updatedLocalHistory,
- currency,
- );
-
- if (
- reconciled.newAddedItems.length + reconciled.newMatchedItems.length !=
- 0
- ) {
- logger.trace("setting reserve status to 'withdrawing' after query");
- r.reserveStatus = ReserveRecordStatus.WITHDRAWING;
- r.retryInfo = initRetryInfo();
- r.requestedQuery = false;
- } else {
- if (r.requestedQuery) {
- logger.trace(
- "setting reserve status to 'querying-status' (requested query) after query",
- );
- r.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
- r.requestedQuery = false;
- r.retryInfo = initRetryInfo();
+ if (!newReserve.initialWithdrawalStarted) {
+ withdrawalGroupId = newReserve.initialWithdrawalGroupId;
+ newReserve.initialWithdrawalStarted = true;
} else {
- logger.trace("setting reserve status to 'dormant' after query");
- r.reserveStatus = ReserveRecordStatus.DORMANT;
- r.retryInfo = initRetryInfo(false);
+ withdrawalGroupId = encodeCrock(randomBytes(32));
}
+
+ const withdrawalRecord: WithdrawalGroupRecord = {
+ withdrawalGroupId: withdrawalGroupId,
+ exchangeBaseUrl: reserve.exchangeBaseUrl,
+ reservePub: reserve.reservePub,
+ rawWithdrawalAmount: remainingAmount,
+ timestampStart: getTimestampNow(),
+ retryInfo: initRetryInfo(),
+ lastError: undefined,
+ denomsSel: denomSelectionInfoToState(denomSelInfo),
+ };
+
+ newReserve.lastError = undefined;
+ newReserve.retryInfo = initRetryInfo(false);
+ newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
+
+ await tx.put(Stores.reserves, newReserve);
+ await tx.put(Stores.withdrawalGroups, withdrawalRecord);
+ return withdrawalRecord;
}
- r.lastSuccessfulStatusQuery = getTimestampNow();
- hist.reserveTransactions = reconciled.updatedLocalHistory;
- r.lastError = undefined;
- await tx.put(Stores.reserves, r);
- await tx.put(Stores.reserveHistory, hist);
+ return;
},
);
- ws.notify({ type: NotificationType.ReserveUpdated, updateSummary });
- const reserve2 = await ws.db.get(Stores.reserves, reservePub);
- if (reserve2) {
- logger.trace(
- `after db transaction, reserve status is ${reserve2.reserveStatus}`,
- );
+
+ if (newWithdrawalGroup) {
+ logger.trace("processing new withdraw group");
+ ws.notify({
+ type: NotificationType.WithdrawGroupCreated,
+ withdrawalGroupId: newWithdrawalGroup.withdrawalGroupId,
+ });
+ await processWithdrawGroup(ws, newWithdrawalGroup.withdrawalGroupId);
+ } else {
+ console.trace("withdraw session already existed");
}
+
return { ready: true };
}
@@ -651,9 +685,6 @@ async function processReserveImpl(
break;
}
}
- case ReserveRecordStatus.WITHDRAWING:
- await depleteReserve(ws, reservePub);
- break;
case ReserveRecordStatus.DORMANT:
// nothing to do
break;
@@ -669,166 +700,6 @@ async function processReserveImpl(
}
}
-/**
- * Withdraw coins from a reserve until it is empty.
- *
- * When finished, marks the reserve as depleted by setting
- * the depleted timestamp.
- */
-async function depleteReserve(
- ws: InternalWalletState,
- reservePub: string,
-): Promise<void> {
- let reserve: ReserveRecord | undefined;
- let hist: ReserveHistoryRecord | undefined;
- await ws.db.runWithReadTransaction(
- [Stores.reserves, Stores.reserveHistory],
- async (tx) => {
- reserve = await tx.get(Stores.reserves, reservePub);
- hist = await tx.get(Stores.reserveHistory, reservePub);
- },
- );
-
- if (!reserve) {
- return;
- }
- if (!hist) {
- throw Error("inconsistent database");
- }
- if (reserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
- return;
- }
- logger.trace(`depleting reserve ${reservePub}`);
-
- const summary = summarizeReserveHistory(
- hist.reserveTransactions,
- reserve.currency,
- );
-
- const withdrawAmount = summary.unclaimedReserveAmount;
-
- const denomsForWithdraw = await selectWithdrawalDenoms(
- ws,
- reserve.exchangeBaseUrl,
- withdrawAmount,
- );
- if (!denomsForWithdraw) {
- // Only complain about inability to withdraw if we
- // didn't withdraw before.
- if (Amounts.isZero(summary.withdrawnAmount)) {
- const opErr = makeErrorDetails(
- TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT,
- `Unable to withdraw from reserve, no denominations are available to withdraw.`,
- {},
- );
- await incrementReserveRetry(ws, reserve.reservePub, opErr);
- throw new OperationFailedAndReportedError(opErr);
- }
- return;
- }
-
- logger.trace(
- `Selected coins total cost ${Amounts.stringify(
- denomsForWithdraw.totalWithdrawCost,
- )} for withdrawal of ${Amounts.stringify(withdrawAmount)}`,
- );
-
- logger.trace("selected denominations");
-
- const newWithdrawalGroup = await ws.db.runWithWriteTransaction(
- [
- Stores.withdrawalGroups,
- Stores.reserves,
- Stores.reserveHistory,
- Stores.planchets,
- ],
- async (tx) => {
- const newReserve = await tx.get(Stores.reserves, reservePub);
- if (!newReserve) {
- return false;
- }
- if (newReserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
- return false;
- }
- const newHist = await tx.get(Stores.reserveHistory, reservePub);
- if (!newHist) {
- throw Error("inconsistent database");
- }
- const newSummary = summarizeReserveHistory(
- newHist.reserveTransactions,
- newReserve.currency,
- );
- if (
- Amounts.cmp(
- newSummary.unclaimedReserveAmount,
- denomsForWithdraw.totalWithdrawCost,
- ) < 0
- ) {
- // Something must have happened concurrently!
- logger.error(
- "aborting withdrawal session, likely concurrent withdrawal happened",
- );
- logger.error(
- `unclaimed reserve amount is ${newSummary.unclaimedReserveAmount}`,
- );
- logger.error(
- `withdrawal cost is ${denomsForWithdraw.totalWithdrawCost}`,
- );
- return false;
- }
- for (let i = 0; i < denomsForWithdraw.selectedDenoms.length; i++) {
- const sd = denomsForWithdraw.selectedDenoms[i];
- for (let j = 0; j < sd.count; j++) {
- const amt = Amounts.add(sd.denom.value, sd.denom.feeWithdraw).amount;
- newHist.reserveTransactions.push({
- type: WalletReserveHistoryItemType.Withdraw,
- expectedAmount: amt,
- });
- }
- }
- logger.trace("setting reserve status to dormant after depletion");
- newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
- newReserve.retryInfo = initRetryInfo(false);
-
- let withdrawalGroupId: string;
-
- if (!newReserve.initialWithdrawalStarted) {
- withdrawalGroupId = newReserve.initialWithdrawalGroupId;
- newReserve.initialWithdrawalStarted = true;
- } else {
- withdrawalGroupId = encodeCrock(randomBytes(32));
- }
-
- const withdrawalRecord: WithdrawalGroupRecord = {
- withdrawalGroupId: withdrawalGroupId,
- exchangeBaseUrl: newReserve.exchangeBaseUrl,
- reservePub: newReserve.reservePub,
- rawWithdrawalAmount: withdrawAmount,
- timestampStart: getTimestampNow(),
- retryInfo: initRetryInfo(),
- lastError: undefined,
- denomsSel: denomSelectionInfoToState(denomsForWithdraw),
- };
-
- await tx.put(Stores.reserves, newReserve);
- await tx.put(Stores.reserveHistory, newHist);
- await tx.put(Stores.withdrawalGroups, withdrawalRecord);
- return withdrawalRecord;
- },
- );
-
- if (newWithdrawalGroup) {
- logger.trace("processing new withdraw group");
- ws.notify({
- type: NotificationType.WithdrawGroupCreated,
- withdrawalGroupId: newWithdrawalGroup.withdrawalGroupId,
- });
- await processWithdrawGroup(ws, newWithdrawalGroup.withdrawalGroupId);
- } else {
- console.trace("withdraw session already existed");
- }
-}
-
export async function createTalerWithdrawReserve(
ws: InternalWalletState,
talerWithdrawUri: string,
diff --git a/packages/taler-wallet-core/src/operations/state.ts b/packages/taler-wallet-core/src/operations/state.ts
index c4d5b38f1..11695f6d0 100644
--- a/packages/taler-wallet-core/src/operations/state.ts
+++ b/packages/taler-wallet-core/src/operations/state.ts
@@ -19,7 +19,7 @@ import { BalancesResponse } from "../types/walletTypes";
import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi";
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo";
import { Logger } from "../util/logging";
-import { PendingOperationsResponse } from "../types/pending";
+import { PendingOperationsResponse } from "../types/pendingTypes";
import { WalletNotification } from "../types/notifications";
import { Database } from "../util/query";
import { openPromise, OpenedPromise } from "../util/promiseUtils";
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts
index bc10e346d..f47f76623 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -32,8 +32,10 @@ import {
} from "../types/dbTypes";
import {
getExchangeWithdrawalInfo,
- selectWithdrawalDenoms,
denomSelectionInfoToState,
+ updateWithdrawalDenoms,
+ getPossibleWithdrawalDenoms,
+ selectWithdrawalDenominations,
} from "./withdraw";
import { updateExchangeFromUrl } from "./exchanges";
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
@@ -92,12 +94,15 @@ export async function prepareTip(
);
const walletTipId = encodeCrock(getRandomBytes(32));
- const selectedDenoms = await selectWithdrawalDenoms(
- ws,
- tipPickupStatus.exchange_url,
+ await updateWithdrawalDenoms(ws, tipPickupStatus.exchange_url);
+ const denoms = await getPossibleWithdrawalDenoms(ws, tipPickupStatus.exchange_url);
+ const selectedDenoms = await selectWithdrawalDenominations(
amount,
+ denoms
);
+ const secretSeed = encodeCrock(getRandomBytes(64));
+
tipRecord = {
walletTipId: walletTipId,
acceptedTimestamp: undefined,
@@ -105,7 +110,6 @@ export async function prepareTip(
tipExpiration: tipPickupStatus.expiration,
exchangeBaseUrl: tipPickupStatus.exchange_url,
merchantBaseUrl: res.merchantBaseUrl,
- planchets: undefined,
createdTimestamp: getTimestampNow(),
merchantTipId: res.merchantTipId,
tipAmountEffective: Amounts.sub(
@@ -117,6 +121,7 @@ export async function prepareTip(
lastError: undefined,
denomsSel: denomSelectionInfoToState(selectedDenoms),
pickedUpTimestamp: undefined,
+ secretSeed,
};
await ws.db.put(Stores.tips, tipRecord);
}
@@ -316,6 +321,7 @@ async function processTipImpl(
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
status: CoinStatus.Fresh,
suspended: false,
+ coinEvHash: planchet.coinEvHash,
});
}
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 56e07a426..cf524db4e 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -36,7 +36,7 @@ import {
WithdrawalType,
WithdrawalDetails,
OrderShortInfo,
-} from "../types/transactions";
+} from "../types/transactionsTypes";
import { getFundingPaytoUris } from "./reserves";
import { TipResponse } from "../types/talerTypes";
diff --git a/packages/taler-wallet-core/src/operations/withdraw-test.ts b/packages/taler-wallet-core/src/operations/withdraw-test.ts
index 24cb6f4b1..d21119c8c 100644
--- a/packages/taler-wallet-core/src/operations/withdraw-test.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw-test.ts
@@ -15,7 +15,7 @@
*/
import test from "ava";
-import { getWithdrawDenomList } from "./withdraw";
+import { selectWithdrawalDenominations } from "./withdraw";
import { Amounts } from "../util/amounts";
test("withdrawal selection bug repro", (t) => {
@@ -322,7 +322,7 @@ test("withdrawal selection bug repro", (t) => {
},
];
- const res = getWithdrawDenomList(amount, denoms);
+ const res = selectWithdrawalDenominations(amount, denoms);
console.error("cost", Amounts.stringify(res.totalWithdrawCost));
console.error("withdraw amount", Amounts.stringify(amount));
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index a3bb9724c..758b80787 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -86,12 +86,13 @@ export function isWithdrawableDenom(d: DenominationRecord): boolean {
return started && stillOkay && !d.isRevoked;
}
+
/**
* Get a list of denominations (with repetitions possible)
* whose total value is as close as possible to the available
* amount, but never larger.
*/
-export function getWithdrawDenomList(
+export function selectWithdrawalDenominations(
amountAvailable: AmountJson,
denoms: DenominationRecord[],
): DenominationSelectionInfo {
@@ -207,7 +208,7 @@ export async function getBankWithdrawalInfo(
/**
* Return denominations that can potentially used for a withdrawal.
*/
-async function getPossibleDenoms(
+export async function getPossibleWithdrawalDenoms(
ws: InternalWalletState,
exchangeBaseUrl: string,
): Promise<DenominationRecord[]> {
@@ -470,6 +471,7 @@ async function processPlanchetVerifyAndStoreCoin(
denomPub: planchet.denomPub,
denomPubHash: planchet.denomPubHash,
denomSig,
+ coinEvHash: planchet.coinEvHash,
exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
status: CoinStatus.Fresh,
coinSource: {
@@ -524,17 +526,13 @@ export function denomSelectionInfoToState(
}
/**
- * Get a list of denominations to withdraw from the given exchange for the
- * given amount, making sure that all denominations' signatures are verified.
- *
- * Writes to the DB in order to record the result from verifying
- * denominations.
+ * Make sure that denominations that currently can be used for withdrawal
+ * are validated, and the result of validation is stored in the database.
*/
-export async function selectWithdrawalDenoms(
+export async function updateWithdrawalDenoms(
ws: InternalWalletState,
exchangeBaseUrl: string,
- amount: AmountJson,
-): Promise<DenominationSelectionInfo> {
+): Promise<void> {
const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) {
logger.error("exchange not found");
@@ -545,43 +543,24 @@ export async function selectWithdrawalDenoms(
logger.error("exchange details not available");
throw Error(`exchange ${exchangeBaseUrl} details not available`);
}
-
- let allValid = false;
- let selectedDenoms: DenominationSelectionInfo;
-
- // Find a denomination selection for the requested amount.
- // If a selected denomination has not been validated yet
- // and turns our to be invalid, we try again with the
- // reduced set of denominations.
- do {
- allValid = true;
- const nextPossibleDenoms = await getPossibleDenoms(ws, exchange.baseUrl);
- selectedDenoms = getWithdrawDenomList(amount, nextPossibleDenoms);
- for (const denomSel of selectedDenoms.selectedDenoms) {
- const denom = denomSel.denom;
- if (denom.status === DenominationStatus.Unverified) {
- const valid = await ws.cryptoApi.isValidDenom(
- denom,
- exchangeDetails.masterPublicKey,
- );
- if (!valid) {
- denom.status = DenominationStatus.VerifiedBad;
- allValid = false;
- } else {
- denom.status = DenominationStatus.VerifiedGood;
- }
- await ws.db.put(Stores.denominations, denom);
+ const denominations = await getPossibleWithdrawalDenoms(ws, exchangeBaseUrl);
+ for (const denom of denominations) {
+ if (denom.status === DenominationStatus.Unverified) {
+ const valid = await ws.cryptoApi.isValidDenom(
+ denom,
+ exchangeDetails.masterPublicKey,
+ );
+ if (!valid) {
+ denom.status = DenominationStatus.VerifiedBad;
+ } else {
+ denom.status = DenominationStatus.VerifiedGood;
}
+ await ws.db.put(Stores.denominations, denom);
}
- } while (selectedDenoms.selectedDenoms.length > 0 && !allValid);
-
- if (Amounts.cmp(selectedDenoms.totalWithdrawCost, amount) > 0) {
- throw Error("Bug: withdrawal coin selection is wrong");
}
-
- return selectedDenoms;
}
+
async function incrementWithdrawalRetry(
ws: InternalWalletState,
withdrawalGroupId: string,
@@ -745,7 +724,9 @@ export async function getExchangeWithdrawalInfo(
throw Error(`exchange ${exchangeInfo.baseUrl} wire details not available`);
}
- const selectedDenoms = await selectWithdrawalDenoms(ws, baseUrl, amount);
+ await updateWithdrawalDenoms(ws, baseUrl);
+ const denoms = await getPossibleWithdrawalDenoms(ws, baseUrl);
+ const selectedDenoms = selectWithdrawalDenominations(amount, denoms);
const exchangeWireAccounts: string[] = [];
for (const account of exchangeWireInfo.accounts) {
exchangeWireAccounts.push(account.payto_uri);