summaryrefslogtreecommitdiff
path: root/packages
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
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')
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts2
-rw-r--r--packages/taler-wallet-core/src/index.ts4
-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
-rw-r--r--packages/taler-wallet-core/src/types/backupTypes.ts82
-rw-r--r--packages/taler-wallet-core/src/types/cryptoTypes.ts6
-rw-r--r--packages/taler-wallet-core/src/types/dbTypes.ts114
-rw-r--r--packages/taler-wallet-core/src/types/notifications.ts6
-rw-r--r--packages/taler-wallet-core/src/types/pending.ts276
-rw-r--r--packages/taler-wallet-core/src/types/transactions.ts337
-rw-r--r--packages/taler-wallet-core/src/types/walletTypes.ts2
-rw-r--r--packages/taler-wallet-core/src/util/query.ts2
-rw-r--r--packages/taler-wallet-core/src/util/reserveHistoryUtil-test.ts285
-rw-r--r--packages/taler-wallet-core/src/util/reserveHistoryUtil.ts363
-rw-r--r--packages/taler-wallet-core/src/wallet.ts6
24 files changed, 248 insertions, 1749 deletions
diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
index deaad42bb..4f553c502 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
@@ -206,6 +206,7 @@ export class CryptoImplementation {
const tipPlanchet: DerivedTipPlanchet = {
blindingKey: encodeCrock(blindingFactor),
coinEv: encodeCrock(ev),
+ coinEvHash: encodeCrock(hash(ev)),
coinPriv: encodeCrock(fc.coinPriv),
coinPub: encodeCrock(fc.coinPub),
};
@@ -463,6 +464,7 @@ export class CryptoImplementation {
coinEv: encodeCrock(ev),
privateKey: encodeCrock(coinPriv),
publicKey: encodeCrock(coinPub),
+ coinEvHash: encodeCrock(hash(ev)),
};
planchets.push(planchet);
diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts
index a94155b14..3d52ed762 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -63,5 +63,5 @@ export * from "./util/time";
export * from "./types/talerTypes";
export * from "./types/walletTypes";
export * from "./types/notifications";
-export * from "./types/transactions";
-export * from "./types/pending";
+export * from "./types/transactionsTypes";
+export * from "./types/pendingTypes";
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);
diff --git a/packages/taler-wallet-core/src/types/backupTypes.ts b/packages/taler-wallet-core/src/types/backupTypes.ts
index 09bc4670c..a3261ae35 100644
--- a/packages/taler-wallet-core/src/types/backupTypes.ts
+++ b/packages/taler-wallet-core/src/types/backupTypes.ts
@@ -53,12 +53,6 @@
* Imports.
*/
import { Timestamp } from "../util/time";
-import {
- ReserveClosingTransaction,
- ReserveCreditTransaction,
- ReserveRecoupTransaction,
- ReserveWithdrawTransaction,
-} from "./ReserveTransaction";
/**
* Type alias for strings that are to be treated like amounts.
@@ -1128,82 +1122,6 @@ export interface BackupExchange {
tos_etag_accepted: string | undefined;
}
-export enum WalletReserveHistoryItemType {
- Credit = "credit",
- Withdraw = "withdraw",
- Closing = "closing",
- Recoup = "recoup",
-}
-
-export interface BackupReserveHistoryCreditItem {
- type: WalletReserveHistoryItemType.Credit;
-
- /**
- * Amount we expect to see credited.
- */
- expected_amount?: BackupAmountString;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matched_exchange_transaction?: ReserveCreditTransaction;
-}
-
-/**
- * Reserve history item for a withdrawal
- */
-export interface BackupReserveHistoryWithdrawItem {
- type: WalletReserveHistoryItemType.Withdraw;
-
- expected_amount?: BackupAmountString;
-
- /**
- * Hash of the blinded coin.
- *
- * When this value is set, it indicates that a withdrawal is active
- * in the wallet for the reserve.
- */
- expected_coin_ev_hash?: string;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matched_exchange_transaction?: ReserveWithdrawTransaction;
-}
-
-export interface BackupReserveHistoryClosingItem {
- type: WalletReserveHistoryItemType.Closing;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matched_exchange_transaction?: ReserveClosingTransaction;
-}
-
-export interface BackupReserveHistoryRecoupItem {
- type: WalletReserveHistoryItemType.Recoup;
-
- /**
- * Amount we expect to see recouped.
- */
- expected_amount?: BackupAmountString;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matched_exchange_transaction?: ReserveRecoupTransaction;
-}
-
-export type BackupReserveHistoryItem =
- | BackupReserveHistoryCreditItem
- | BackupReserveHistoryWithdrawItem
- | BackupReserveHistoryRecoupItem
- | BackupReserveHistoryClosingItem;
-
export enum BackupProposalStatus {
/**
* Proposed (and either downloaded or not,
diff --git a/packages/taler-wallet-core/src/types/cryptoTypes.ts b/packages/taler-wallet-core/src/types/cryptoTypes.ts
index 98bdf92ec..eb18d83fc 100644
--- a/packages/taler-wallet-core/src/types/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/types/cryptoTypes.ts
@@ -84,6 +84,11 @@ export interface DerivedRefreshSession {
coinEv: string;
/**
+ * Hash of the blinded public key.
+ */
+ coinEvHash: string;
+
+ /**
* Blinding key used.
*/
blindingKey: string;
@@ -122,6 +127,7 @@ export interface DeriveTipRequest {
export interface DerivedTipPlanchet {
blindingKey: string;
coinEv: string;
+ coinEvHash: string;
coinPriv: string;
coinPub: string;
}
diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts
index 3a42b8dbc..71a591310 100644
--- a/packages/taler-wallet-core/src/types/dbTypes.ts
+++ b/packages/taler-wallet-core/src/types/dbTypes.ts
@@ -66,12 +66,6 @@ export enum ReserveRecordStatus {
QUERYING_STATUS = "querying-status",
/**
- * Status is queried, the wallet must now select coins
- * and start withdrawing.
- */
- WITHDRAWING = "withdrawing",
-
- /**
* The corresponding withdraw record has been created.
* No further processing is done, unless explicitly requested
* by the user.
@@ -84,76 +78,6 @@ export enum ReserveRecordStatus {
BANK_ABORTED = "bank-aborted",
}
-export enum WalletReserveHistoryItemType {
- Credit = "credit",
- Withdraw = "withdraw",
- Closing = "closing",
- Recoup = "recoup",
-}
-
-export interface WalletReserveHistoryCreditItem {
- type: WalletReserveHistoryItemType.Credit;
-
- /**
- * Amount we expect to see credited.
- */
- expectedAmount?: AmountJson;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matchedExchangeTransaction?: ReserveCreditTransaction;
-}
-
-export interface WalletReserveHistoryWithdrawItem {
- expectedAmount?: AmountJson;
-
- type: WalletReserveHistoryItemType.Withdraw;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matchedExchangeTransaction?: ReserveWithdrawTransaction;
-}
-
-export interface WalletReserveHistoryClosingItem {
- type: WalletReserveHistoryItemType.Closing;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matchedExchangeTransaction?: ReserveClosingTransaction;
-}
-
-export interface WalletReserveHistoryRecoupItem {
- type: WalletReserveHistoryItemType.Recoup;
-
- /**
- * Amount we expect to see recouped.
- */
- expectedAmount?: AmountJson;
-
- /**
- * Item from the reserve transaction history that this
- * wallet reserve history item matches up with.
- */
- matchedExchangeTransaction?: ReserveRecoupTransaction;
-}
-
-export type WalletReserveHistoryItem =
- | WalletReserveHistoryCreditItem
- | WalletReserveHistoryWithdrawItem
- | WalletReserveHistoryRecoupItem
- | WalletReserveHistoryClosingItem;
-
-export interface ReserveHistoryRecord {
- reservePub: string;
- reserveTransactions: WalletReserveHistoryItem[];
-}
-
export interface ReserveBankInfo {
/**
* Status URL that the wallet will use to query the status
@@ -667,6 +591,8 @@ export interface RefreshPlanchet {
*/
coinEv: string;
+ coinEvHash: string;
+
/**
* Blinding key used.
*/
@@ -783,6 +709,14 @@ export interface CoinRecord {
blindingKey: string;
/**
+ * Hash of the coin envelope.
+ *
+ * Stored here for indexing purposes, so that when looking at a
+ * reserve history, we can quickly find the coin for a withdrawal transaction.
+ */
+ coinEvHash: string;
+
+ /**
* Status of the coin.
*/
status: CoinStatus;
@@ -1536,6 +1470,12 @@ class CoinsStore extends Store<"coins", CoinRecord> {
string,
CoinRecord
>(this, "denomPubHashIndex", "denomPubHash");
+
+ coinEvHashIndex = new Index<"coins", "coinEvHashIndex", string, CoinRecord>(
+ this,
+ "coinEvHashIndex",
+ "coinEvHash",
+ );
}
class ProposalsStore extends Store<"proposals", ProposalRecord> {
@@ -1602,15 +1542,6 @@ class ReservesStore extends Store<"reserves", ReserveRecord> {
}
}
-class ReserveHistoryStore extends Store<
- "reserveHistory",
- ReserveHistoryRecord
-> {
- constructor() {
- super("reserveHistory", { keyPath: "reservePub" });
- }
-}
-
class TipsStore extends Store<"tips", TipRecord> {
constructor() {
super("tips", { keyPath: "walletTipId" });
@@ -1638,6 +1569,12 @@ class WithdrawalGroupsStore extends Store<
constructor() {
super("withdrawals", { keyPath: "withdrawalGroupId" });
}
+ byReservePub = new Index<
+ "withdrawals",
+ "withdrawalsByReserveIndex",
+ string,
+ WithdrawalGroupRecord
+ >(this, "withdrawalsByReserveIndex", "reservePub");
}
class PlanchetsStore extends Store<"planchets", PlanchetRecord> {
@@ -1656,6 +1593,12 @@ class PlanchetsStore extends Store<"planchets", PlanchetRecord> {
string,
PlanchetRecord
>(this, "withdrawalGroupIndex", "withdrawalGroupId");
+
+ coinEvHashIndex = new Index<"planchets", "coinEvHashIndex", string, PlanchetRecord>(
+ this,
+ "coinEvHashIndex",
+ "coinEvHash",
+ );
}
/**
@@ -1702,7 +1645,6 @@ export const Stores = {
keyPath: "recoupGroupId",
}),
reserves: new ReservesStore(),
- reserveHistory: new ReserveHistoryStore(),
purchases: new PurchasesStore(),
tips: new TipsStore(),
withdrawalGroups: new WithdrawalGroupsStore(),
diff --git a/packages/taler-wallet-core/src/types/notifications.ts b/packages/taler-wallet-core/src/types/notifications.ts
index 533223cc0..9ddcf4fa2 100644
--- a/packages/taler-wallet-core/src/types/notifications.ts
+++ b/packages/taler-wallet-core/src/types/notifications.ts
@@ -23,7 +23,6 @@
* Imports.
*/
import { TalerErrorDetails } from "./walletTypes";
-import { ReserveHistorySummary } from "../util/reserveHistoryUtil";
export enum NotificationType {
CoinWithdrawn = "coin-withdrawn",
@@ -125,10 +124,6 @@ export interface RefreshRefusedNotification {
type: NotificationType.RefreshUnwarranted;
}
-export interface ReserveUpdatedNotification {
- type: NotificationType.ReserveUpdated;
- updateSummary?: ReserveHistorySummary;
-}
export interface ReserveConfirmedNotification {
type: NotificationType.ReserveConfirmed;
@@ -252,7 +247,6 @@ export type WalletNotification =
| RefreshRevealedNotification
| RefreshStartedNotification
| RefreshRefusedNotification
- | ReserveUpdatedNotification
| ReserveCreatedNotification
| ReserveConfirmedNotification
| WithdrawalGroupFinishedNotification
diff --git a/packages/taler-wallet-core/src/types/pending.ts b/packages/taler-wallet-core/src/types/pending.ts
deleted file mode 100644
index 18d9a2fa4..000000000
--- a/packages/taler-wallet-core/src/types/pending.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Type and schema definitions for pending operations in the wallet.
- */
-
-/**
- * Imports.
- */
-import { TalerErrorDetails, BalancesResponse } from "./walletTypes";
-import { ReserveRecordStatus } from "./dbTypes";
-import { Timestamp, Duration } from "../util/time";
-import { RetryInfo } from "../util/retries";
-
-export enum PendingOperationType {
- Bug = "bug",
- ExchangeUpdate = "exchange-update",
- ExchangeCheckRefresh = "exchange-check-refresh",
- Pay = "pay",
- ProposalChoice = "proposal-choice",
- ProposalDownload = "proposal-download",
- Refresh = "refresh",
- Reserve = "reserve",
- Recoup = "recoup",
- RefundQuery = "refund-query",
- TipChoice = "tip-choice",
- TipPickup = "tip-pickup",
- Withdraw = "withdraw",
-}
-
-/**
- * Information about a pending operation.
- */
-export type PendingOperationInfo = PendingOperationInfoCommon &
- (
- | PendingBugOperation
- | PendingExchangeUpdateOperation
- | PendingExchangeCheckRefreshOperation
- | PendingPayOperation
- | PendingProposalChoiceOperation
- | PendingProposalDownloadOperation
- | PendingRefreshOperation
- | PendingRefundQueryOperation
- | PendingReserveOperation
- | PendingTipChoiceOperation
- | PendingTipPickupOperation
- | PendingWithdrawOperation
- | PendingRecoupOperation
- );
-
-/**
- * The wallet is currently updating information about an exchange.
- */
-export interface PendingExchangeUpdateOperation {
- type: PendingOperationType.ExchangeUpdate;
- stage: ExchangeUpdateOperationStage;
- reason: string;
- exchangeBaseUrl: string;
- lastError: TalerErrorDetails | undefined;
-}
-
-/**
- * The wallet should check whether coins from this exchange
- * need to be auto-refreshed.
- */
-export interface PendingExchangeCheckRefreshOperation {
- type: PendingOperationType.ExchangeCheckRefresh;
- exchangeBaseUrl: string;
-}
-
-/**
- * Some interal error happened in the wallet. This pending operation
- * should *only* be reported for problems in the wallet, not when
- * a problem with a merchant/exchange/etc. occurs.
- */
-export interface PendingBugOperation {
- type: PendingOperationType.Bug;
- message: string;
- details: any;
-}
-
-/**
- * Current state of an exchange update operation.
- */
-export enum ExchangeUpdateOperationStage {
- FetchKeys = "fetch-keys",
- FetchWire = "fetch-wire",
- FinalizeUpdate = "finalize-update",
-}
-
-export enum ReserveType {
- /**
- * Manually created.
- */
- Manual = "manual",
- /**
- * Withdrawn from a bank that has "tight" Taler integration
- */
- TalerBankWithdraw = "taler-bank-withdraw",
-}
-
-/**
- * Status of processing a reserve.
- *
- * Does *not* include the withdrawal operation that might result
- * from this.
- */
-export interface PendingReserveOperation {
- type: PendingOperationType.Reserve;
- retryInfo: RetryInfo | undefined;
- stage: ReserveRecordStatus;
- timestampCreated: Timestamp;
- reserveType: ReserveType;
- reservePub: string;
- bankWithdrawConfirmUrl?: string;
-}
-
-/**
- * Status of an ongoing withdrawal operation.
- */
-export interface PendingRefreshOperation {
- type: PendingOperationType.Refresh;
- lastError?: TalerErrorDetails;
- refreshGroupId: string;
- finishedPerCoin: boolean[];
- retryInfo: RetryInfo;
-}
-
-/**
- * Status of downloading signed contract terms from a merchant.
- */
-export interface PendingProposalDownloadOperation {
- type: PendingOperationType.ProposalDownload;
- merchantBaseUrl: string;
- proposalTimestamp: Timestamp;
- proposalId: string;
- orderId: string;
- lastError?: TalerErrorDetails;
- retryInfo: RetryInfo;
-}
-
-/**
- * User must choose whether to accept or reject the merchant's
- * proposed contract terms.
- */
-export interface PendingProposalChoiceOperation {
- type: PendingOperationType.ProposalChoice;
- merchantBaseUrl: string;
- proposalTimestamp: Timestamp;
- proposalId: string;
-}
-
-/**
- * The wallet is picking up a tip that the user has accepted.
- */
-export interface PendingTipPickupOperation {
- type: PendingOperationType.TipPickup;
- tipId: string;
- merchantBaseUrl: string;
- merchantTipId: string;
-}
-
-/**
- * The wallet has been offered a tip, and the user now needs to
- * decide whether to accept or reject the tip.
- */
-export interface PendingTipChoiceOperation {
- type: PendingOperationType.TipChoice;
- tipId: string;
- merchantBaseUrl: string;
- merchantTipId: string;
-}
-
-/**
- * The wallet is signing coins and then sending them to
- * the merchant.
- */
-export interface PendingPayOperation {
- type: PendingOperationType.Pay;
- proposalId: string;
- isReplay: boolean;
- retryInfo: RetryInfo;
- lastError: TalerErrorDetails | undefined;
-}
-
-/**
- * The wallet is querying the merchant about whether any refund
- * permissions are available for a purchase.
- */
-export interface PendingRefundQueryOperation {
- type: PendingOperationType.RefundQuery;
- proposalId: string;
- retryInfo: RetryInfo;
- lastError: TalerErrorDetails | undefined;
-}
-
-export interface PendingRecoupOperation {
- type: PendingOperationType.Recoup;
- recoupGroupId: string;
- retryInfo: RetryInfo;
- lastError: TalerErrorDetails | undefined;
-}
-
-/**
- * Status of an ongoing withdrawal operation.
- */
-export interface PendingWithdrawOperation {
- type: PendingOperationType.Withdraw;
- lastError: TalerErrorDetails | undefined;
- retryInfo: RetryInfo;
- withdrawalGroupId: string;
- numCoinsWithdrawn: number;
- numCoinsTotal: number;
-}
-
-/**
- * Fields that are present in every pending operation.
- */
-export interface PendingOperationInfoCommon {
- /**
- * Type of the pending operation.
- */
- type: PendingOperationType;
-
- /**
- * Set to true if the operation indicates that something is really in progress,
- * as opposed to some regular scheduled operation or a permanent failure.
- */
- givesLifeness: boolean;
-
- /**
- * Retry info, not available on all pending operations.
- * If it is available, it must have the same name.
- */
- retryInfo?: RetryInfo;
-}
-
-/**
- * Response returned from the pending operations API.
- */
-export interface PendingOperationsResponse {
- /**
- * List of pending operations.
- */
- pendingOperations: PendingOperationInfo[];
-
- /**
- * Current wallet balance, including pending balances.
- */
- walletBalance: BalancesResponse;
-
- /**
- * When is the next pending operation due to be re-tried?
- */
- nextRetryDelay: Duration;
-
- /**
- * Does this response only include pending operations that
- * are due to be executed right now?
- */
- onlyDue: boolean;
-}
diff --git a/packages/taler-wallet-core/src/types/transactions.ts b/packages/taler-wallet-core/src/types/transactions.ts
deleted file mode 100644
index 0a683f298..000000000
--- a/packages/taler-wallet-core/src/types/transactions.ts
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Type and schema definitions for the wallet's transaction list.
- *
- * @author Florian Dold
- * @author Torsten Grote
- */
-
-/**
- * Imports.
- */
-import { Timestamp } from "../util/time";
-import {
- AmountString,
- Product,
- InternationalizedString,
- MerchantInfo,
- codecForInternationalizedString,
- codecForMerchantInfo,
- codecForProduct,
-} from "./talerTypes";
-import {
- Codec,
- buildCodecForObject,
- codecOptional,
- codecForString,
- codecForList,
- codecForAny,
-} from "../util/codec";
-import { TalerErrorDetails } from "./walletTypes";
-
-export interface TransactionsRequest {
- /**
- * return only transactions in the given currency
- */
- currency?: string;
-
- /**
- * if present, results will be limited to transactions related to the given search string
- */
- search?: string;
-}
-
-export interface TransactionsResponse {
- // a list of past and pending transactions sorted by pending, timestamp and transactionId.
- // In case two events are both pending and have the same timestamp,
- // they are sorted by the transactionId
- // (lexically ascending and locale-independent comparison).
- transactions: Transaction[];
-}
-
-export interface TransactionCommon {
- // opaque unique ID for the transaction, used as a starting point for paginating queries
- // and for invoking actions on the transaction (e.g. deleting/hiding it from the history)
- transactionId: string;
-
- // the type of the transaction; different types might provide additional information
- type: TransactionType;
-
- // main timestamp of the transaction
- timestamp: Timestamp;
-
- // true if the transaction is still pending, false otherwise
- // If a transaction is not longer pending, its timestamp will be updated,
- // but its transactionId will remain unchanged
- pending: boolean;
-
- // Raw amount of the transaction (exclusive of fees or other extra costs)
- amountRaw: AmountString;
-
- // Amount added or removed from the wallet's balance (including all fees and other costs)
- amountEffective: AmountString;
-
- error?: TalerErrorDetails;
-}
-
-export type Transaction =
- | TransactionWithdrawal
- | TransactionPayment
- | TransactionRefund
- | TransactionTip
- | TransactionRefresh;
-
-export enum TransactionType {
- Withdrawal = "withdrawal",
- Payment = "payment",
- Refund = "refund",
- Refresh = "refresh",
- Tip = "tip",
-}
-
-export enum WithdrawalType {
- TalerBankIntegrationApi = "taler-bank-integration-api",
- ManualTransfer = "manual-transfer",
-}
-
-export type WithdrawalDetails =
- | WithdrawalDetailsForManualTransfer
- | WithdrawalDetailsForTalerBankIntegrationApi;
-
-interface WithdrawalDetailsForManualTransfer {
- type: WithdrawalType.ManualTransfer;
-
- /**
- * Payto URIs that the exchange supports.
- *
- * Already contains the amount and message.
- */
- exchangePaytoUris: string[];
-}
-
-interface WithdrawalDetailsForTalerBankIntegrationApi {
- type: WithdrawalType.TalerBankIntegrationApi;
-
- /**
- * Set to true if the bank has confirmed the withdrawal, false if not.
- * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI.
- * See also bankConfirmationUrl below.
- */
- confirmed: boolean;
-
- /**
- * If the withdrawal is unconfirmed, this can include a URL for user
- * initiated confirmation.
- */
- bankConfirmationUrl?: string;
-}
-
-// This should only be used for actual withdrawals
-// and not for tips that have their own transactions type.
-interface TransactionWithdrawal extends TransactionCommon {
- type: TransactionType.Withdrawal;
-
- /**
- * Exchange of the withdrawal.
- */
- exchangeBaseUrl: string;
-
- /**
- * Amount that got subtracted from the reserve balance.
- */
- amountRaw: AmountString;
-
- /**
- * Amount that actually was (or will be) added to the wallet's balance.
- */
- amountEffective: AmountString;
-
- withdrawalDetails: WithdrawalDetails;
-}
-
-export enum PaymentStatus {
- /**
- * Explicitly aborted after timeout / failure
- */
- Aborted = "aborted",
-
- /**
- * Payment failed, wallet will auto-retry.
- * User should be given the option to retry now / abort.
- */
- Failed = "failed",
-
- /**
- * Paid successfully
- */
- Paid = "paid",
-
- /**
- * User accepted, payment is processing.
- */
- Accepted = "accepted",
-}
-
-export interface TransactionPayment extends TransactionCommon {
- type: TransactionType.Payment;
-
- /**
- * Additional information about the payment.
- */
- info: OrderShortInfo;
-
- /**
- * Wallet-internal end-to-end identifier for the payment.
- */
- proposalId: string;
-
- /**
- * How far did the wallet get with processing the payment?
- */
- status: PaymentStatus;
-
- /**
- * Amount that must be paid for the contract
- */
- amountRaw: AmountString;
-
- /**
- * Amount that was paid, including deposit, wire and refresh fees.
- */
- amountEffective: AmountString;
-}
-
-export interface OrderShortInfo {
- /**
- * Order ID, uniquely identifies the order within a merchant instance
- */
- orderId: string;
-
- /**
- * Hash of the contract terms.
- */
- contractTermsHash: string;
-
- /**
- * More information about the merchant
- */
- merchant: MerchantInfo;
-
- /**
- * Summary of the order, given by the merchant
- */
- summary: string;
-
- /**
- * Map from IETF BCP 47 language tags to localized summaries
- */
- summary_i18n?: InternationalizedString;
-
- /**
- * List of products that are part of the order
- */
- products: Product[] | undefined;
-
- /**
- * URL of the fulfillment, given by the merchant
- */
- fulfillmentUrl?: string;
-
- /**
- * Plain text message that should be shown to the user
- * when the payment is complete.
- */
- fulfillmentMessage?: string;
-
- /**
- * Translations of fulfillmentMessage.
- */
- fulfillmentMessage_i18n?: InternationalizedString;
-}
-
-interface TransactionRefund extends TransactionCommon {
- type: TransactionType.Refund;
-
- // ID for the transaction that is refunded
- refundedTransactionId: string;
-
- // Additional information about the refunded payment
- info: OrderShortInfo;
-
- // Amount that has been refunded by the merchant
- amountRaw: AmountString;
-
- // Amount will be added to the wallet's balance after fees and refreshing
- amountEffective: AmountString;
-}
-
-interface TransactionTip extends TransactionCommon {
- type: TransactionType.Tip;
-
- // Raw amount of the tip, without extra fees that apply
- amountRaw: AmountString;
-
- // Amount will be (or was) added to the wallet's balance after fees and refreshing
- amountEffective: AmountString;
-
- merchantBaseUrl: string;
-}
-
-// A transaction shown for refreshes that are not associated to other transactions
-// such as a refresh necessary before coin expiration.
-// It should only be returned by the API if the effective amount is different from zero.
-interface TransactionRefresh extends TransactionCommon {
- type: TransactionType.Refresh;
-
- // Exchange that the coins are refreshed with
- exchangeBaseUrl: string;
-
- // Raw amount that is refreshed
- amountRaw: AmountString;
-
- // Amount that will be paid as fees for the refresh
- amountEffective: AmountString;
-}
-
-export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
- buildCodecForObject<TransactionsRequest>()
- .property("currency", codecOptional(codecForString()))
- .property("search", codecOptional(codecForString()))
- .build("TransactionsRequest");
-
-// FIXME: do full validation here!
-export const codecForTransactionsResponse = (): Codec<TransactionsResponse> =>
- buildCodecForObject<TransactionsResponse>()
- .property("transactions", codecForList(codecForAny()))
- .build("TransactionsResponse");
-
-export const codecForOrderShortInfo = (): Codec<OrderShortInfo> =>
- buildCodecForObject<OrderShortInfo>()
- .property("contractTermsHash", codecForString())
- .property("fulfillmentMessage", codecOptional(codecForString()))
- .property(
- "fulfillmentMessage_i18n",
- codecOptional(codecForInternationalizedString()),
- )
- .property("fulfillmentUrl", codecOptional(codecForString()))
- .property("merchant", codecForMerchantInfo())
- .property("orderId", codecForString())
- .property("products", codecOptional(codecForList(codecForProduct())))
- .property("summary", codecForString())
- .property("summary_i18n", codecOptional(codecForInternationalizedString()))
- .build("OrderShortInfo");
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts
index d0e72b289..7dc675b38 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -55,7 +55,7 @@ import {
codecForContractTerms,
ContractTerms,
} from "./talerTypes";
-import { OrderShortInfo, codecForOrderShortInfo } from "./transactions";
+import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes";
/**
* Response for the create reserve request to the wallet.
diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts
index e1a23b168..35aab81e9 100644
--- a/packages/taler-wallet-core/src/util/query.ts
+++ b/packages/taler-wallet-core/src/util/query.ts
@@ -583,7 +583,7 @@ export class Database {
}
async getIndexed<Ind extends Index<string, string, any, any>>(
- index: InferIndex<Ind>,
+ index: Ind,
key: IDBValidKey,
): Promise<IndexRecord<Ind> | undefined> {
const tx = this.db.transaction([index.storeName], "readonly");
diff --git a/packages/taler-wallet-core/src/util/reserveHistoryUtil-test.ts b/packages/taler-wallet-core/src/util/reserveHistoryUtil-test.ts
deleted file mode 100644
index 79022de77..000000000
--- a/packages/taler-wallet-core/src/util/reserveHistoryUtil-test.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports.
- */
-import test from "ava";
-import {
- reconcileReserveHistory,
- summarizeReserveHistory,
-} from "./reserveHistoryUtil";
-import {
- WalletReserveHistoryItem,
- WalletReserveHistoryItemType,
-} from "../types/dbTypes";
-import {
- ReserveTransaction,
- ReserveTransactionType,
-} from "../types/ReserveTransaction";
-import { Amounts } from "./amounts";
-
-test("basics", (t) => {
- const r = reconcileReserveHistory([], []);
- t.deepEqual(r.updatedLocalHistory, []);
-});
-
-test("unmatched credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 1);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:100");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:100");
-});
-
-test("unmatched credit #2", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:150");
-});
-
-test("matched credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- matchedExchangeTransaction: {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:150");
-});
-
-test("fulfilling credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
-});
-
-test("unfulfilled credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:50",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC02",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:150");
-});
-
-test("awaited credit", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:50"),
- },
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:100");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:50");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:100");
-});
-
-test("withdrawal new match", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- matchedExchangeTransaction: {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- },
- {
- type: WalletReserveHistoryItemType.Withdraw,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:5"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- {
- type: ReserveTransactionType.Withdraw,
- amount: "TESTKUDOS:5",
- h_coin_envelope: "foobar",
- h_denom_pub: "foobar",
- reserve_sig: "foobar",
- withdraw_fee: "TESTKUDOS:0.1",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:95");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:95");
-});
-
-test("claimed but now arrived", (t) => {
- const localHistory: WalletReserveHistoryItem[] = [
- {
- type: WalletReserveHistoryItemType.Credit,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:100"),
- matchedExchangeTransaction: {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- },
- {
- type: WalletReserveHistoryItemType.Withdraw,
- expectedAmount: Amounts.parseOrThrow("TESTKUDOS:5"),
- },
- ];
- const remoteHistory: ReserveTransaction[] = [
- {
- type: ReserveTransactionType.Credit,
- amount: "TESTKUDOS:100",
- sender_account_url: "payto://void/",
- timestamp: { t_ms: 42 },
- wire_reference: "ABC01",
- },
- ];
- const r = reconcileReserveHistory(localHistory, remoteHistory);
- const s = summarizeReserveHistory(r.updatedLocalHistory, "TESTKUDOS");
- t.deepEqual(r.updatedLocalHistory.length, 2);
- t.deepEqual(Amounts.stringify(s.computedReserveBalance), "TESTKUDOS:100");
- t.deepEqual(Amounts.stringify(s.awaitedReserveAmount), "TESTKUDOS:0");
- t.deepEqual(Amounts.stringify(s.unclaimedReserveAmount), "TESTKUDOS:95");
-});
diff --git a/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts b/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts
deleted file mode 100644
index 60823e1e0..000000000
--- a/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Helpers for dealing with reserve histories.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-import {
- WalletReserveHistoryItem,
- WalletReserveHistoryItemType,
-} from "../types/dbTypes";
-import {
- ReserveTransaction,
- ReserveTransactionType,
-} from "../types/ReserveTransaction";
-import * as Amounts from "../util/amounts";
-import { timestampCmp } from "./time";
-import { deepCopy } from "./helpers";
-import { AmountJson } from "../util/amounts";
-
-/**
- * Result of a reserve reconciliation.
- */
-export interface ReserveReconciliationResult {
- /**
- * The wallet's local history reconciled with the exchange's reserve history.
- */
- updatedLocalHistory: WalletReserveHistoryItem[];
-
- /**
- * History items that were newly created, subset of the
- * updatedLocalHistory items.
- */
- newAddedItems: WalletReserveHistoryItem[];
-
- /**
- * History items that were newly matched, subset of the
- * updatedLocalHistory items.
- */
- newMatchedItems: WalletReserveHistoryItem[];
-}
-
-/**
- * Various totals computed from the wallet's view
- * on the reserve history.
- */
-export interface ReserveHistorySummary {
- /**
- * Balance computed by the wallet, should match the balance
- * computed by the reserve.
- */
- computedReserveBalance: Amounts.AmountJson;
-
- /**
- * Reserve balance that is still available for withdrawal.
- */
- unclaimedReserveAmount: Amounts.AmountJson;
-
- /**
- * Amount that we're still expecting to come into the reserve.
- */
- awaitedReserveAmount: Amounts.AmountJson;
-
- /**
- * Amount withdrawn from the reserve so far. Only counts
- * finished withdrawals, not withdrawals in progress.
- */
- withdrawnAmount: Amounts.AmountJson;
-}
-
-/**
- * Check if two reserve history items (exchange's version) match.
- */
-function isRemoteHistoryMatch(
- t1: ReserveTransaction,
- t2: ReserveTransaction,
-): boolean {
- switch (t1.type) {
- case ReserveTransactionType.Closing: {
- return t1.type === t2.type && t1.wtid == t2.wtid;
- }
- case ReserveTransactionType.Credit: {
- return t1.type === t2.type && t1.wire_reference === t2.wire_reference;
- }
- case ReserveTransactionType.Recoup: {
- return (
- t1.type === t2.type &&
- t1.coin_pub === t2.coin_pub &&
- timestampCmp(t1.timestamp, t2.timestamp) === 0
- );
- }
- case ReserveTransactionType.Withdraw: {
- return t1.type === t2.type && t1.h_coin_envelope === t2.h_coin_envelope;
- }
- }
-}
-
-/**
- * Check a local reserve history item and a remote history item are a match.
- */
-export function isLocalRemoteHistoryMatch(
- t1: WalletReserveHistoryItem,
- t2: ReserveTransaction,
-): boolean {
- switch (t1.type) {
- case WalletReserveHistoryItemType.Credit: {
- return (
- t2.type === ReserveTransactionType.Credit &&
- !!t1.expectedAmount &&
- Amounts.cmp(t1.expectedAmount, Amounts.parseOrThrow(t2.amount)) === 0
- );
- }
- case WalletReserveHistoryItemType.Withdraw:
- return (
- t2.type === ReserveTransactionType.Withdraw &&
- !!t1.expectedAmount &&
- Amounts.cmp(t1.expectedAmount, Amounts.parseOrThrow(t2.amount)) === 0
- );
- case WalletReserveHistoryItemType.Recoup: {
- return (
- t2.type === ReserveTransactionType.Recoup &&
- !!t1.expectedAmount &&
- Amounts.cmp(t1.expectedAmount, Amounts.parseOrThrow(t2.amount)) === 0
- );
- }
- }
- return false;
-}
-
-/**
- * Compute totals for the wallet's view of the reserve history.
- */
-export function summarizeReserveHistory(
- localHistory: WalletReserveHistoryItem[],
- currency: string,
-): ReserveHistorySummary {
- const posAmounts: AmountJson[] = [];
- const negAmounts: AmountJson[] = [];
- const expectedPosAmounts: AmountJson[] = [];
- const expectedNegAmounts: AmountJson[] = [];
- const withdrawnAmounts: AmountJson[] = [];
-
- for (const item of localHistory) {
- switch (item.type) {
- case WalletReserveHistoryItemType.Credit:
- if (item.matchedExchangeTransaction) {
- posAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else if (item.expectedAmount) {
- expectedPosAmounts.push(item.expectedAmount);
- }
- break;
- case WalletReserveHistoryItemType.Recoup:
- if (item.matchedExchangeTransaction) {
- if (item.matchedExchangeTransaction) {
- posAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else if (item.expectedAmount) {
- expectedPosAmounts.push(item.expectedAmount);
- } else {
- throw Error("invariant failed");
- }
- }
- break;
- case WalletReserveHistoryItemType.Closing:
- if (item.matchedExchangeTransaction) {
- negAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else {
- throw Error("invariant failed");
- }
- break;
- case WalletReserveHistoryItemType.Withdraw:
- if (item.matchedExchangeTransaction) {
- negAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- withdrawnAmounts.push(
- Amounts.parseOrThrow(item.matchedExchangeTransaction.amount),
- );
- } else if (item.expectedAmount) {
- expectedNegAmounts.push(item.expectedAmount);
- } else {
- throw Error("invariant failed");
- }
- break;
- }
- }
-
- const z = Amounts.getZero(currency);
-
- const computedBalance = Amounts.sub(
- Amounts.add(z, ...posAmounts).amount,
- ...negAmounts,
- ).amount;
-
- const unclaimedReserveAmount = Amounts.sub(
- Amounts.add(z, ...posAmounts).amount,
- ...negAmounts,
- ...expectedNegAmounts,
- ).amount;
-
- const awaitedReserveAmount = Amounts.sub(
- Amounts.add(z, ...expectedPosAmounts).amount,
- ...expectedNegAmounts,
- ).amount;
-
- const withdrawnAmount = Amounts.add(z, ...withdrawnAmounts).amount;
-
- return {
- computedReserveBalance: computedBalance,
- unclaimedReserveAmount: unclaimedReserveAmount,
- awaitedReserveAmount: awaitedReserveAmount,
- withdrawnAmount,
- };
-}
-
-/**
- * Reconcile the wallet's local model of the reserve history
- * with the reserve history of the exchange.
- */
-export function reconcileReserveHistory(
- localHistory: WalletReserveHistoryItem[],
- remoteHistory: ReserveTransaction[],
-): ReserveReconciliationResult {
- const updatedLocalHistory: WalletReserveHistoryItem[] = deepCopy(
- localHistory,
- );
- const newMatchedItems: WalletReserveHistoryItem[] = [];
- const newAddedItems: WalletReserveHistoryItem[] = [];
-
- const remoteMatched = remoteHistory.map(() => false);
- const localMatched = localHistory.map(() => false);
-
- // Take care of deposits
-
- // First, see which pairs are already a definite match.
- for (let remoteIndex = 0; remoteIndex < remoteHistory.length; remoteIndex++) {
- const rhi = remoteHistory[remoteIndex];
- for (let localIndex = 0; localIndex < localHistory.length; localIndex++) {
- if (localMatched[localIndex]) {
- continue;
- }
- const lhi = localHistory[localIndex];
- if (!lhi.matchedExchangeTransaction) {
- continue;
- }
- if (isRemoteHistoryMatch(rhi, lhi.matchedExchangeTransaction)) {
- localMatched[localIndex] = true;
- remoteMatched[remoteIndex] = true;
- break;
- }
- }
- }
-
- // Check that all previously matched items are still matched
- for (let localIndex = 0; localIndex < localHistory.length; localIndex++) {
- if (localMatched[localIndex]) {
- continue;
- }
- const lhi = localHistory[localIndex];
- if (lhi.matchedExchangeTransaction) {
- // Don't use for further matching
- localMatched[localIndex] = true;
- // FIXME: emit some error here!
- throw Error("previously matched reserve history item now unmatched");
- }
- }
-
- // Next, find out if there are any exact new matches between local and remote
- // history items
- for (let localIndex = 0; localIndex < localHistory.length; localIndex++) {
- if (localMatched[localIndex]) {
- continue;
- }
- const lhi = localHistory[localIndex];
- for (
- let remoteIndex = 0;
- remoteIndex < remoteHistory.length;
- remoteIndex++
- ) {
- const rhi = remoteHistory[remoteIndex];
- if (remoteMatched[remoteIndex]) {
- continue;
- }
- if (isLocalRemoteHistoryMatch(lhi, rhi)) {
- localMatched[localIndex] = true;
- remoteMatched[remoteIndex] = true;
- updatedLocalHistory[localIndex].matchedExchangeTransaction = rhi as any;
- newMatchedItems.push(lhi);
- break;
- }
- }
- }
-
- // Finally we add new history items
- for (let remoteIndex = 0; remoteIndex < remoteHistory.length; remoteIndex++) {
- if (remoteMatched[remoteIndex]) {
- continue;
- }
- const rhi = remoteHistory[remoteIndex];
- let newItem: WalletReserveHistoryItem;
- switch (rhi.type) {
- case ReserveTransactionType.Closing: {
- newItem = {
- type: WalletReserveHistoryItemType.Closing,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- case ReserveTransactionType.Credit: {
- newItem = {
- type: WalletReserveHistoryItemType.Credit,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- case ReserveTransactionType.Recoup: {
- newItem = {
- type: WalletReserveHistoryItemType.Recoup,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- case ReserveTransactionType.Withdraw: {
- newItem = {
- type: WalletReserveHistoryItemType.Withdraw,
- matchedExchangeTransaction: rhi,
- };
- break;
- }
- }
- updatedLocalHistory.push(newItem);
- newAddedItems.push(newItem);
- }
-
- return {
- updatedLocalHistory,
- newAddedItems,
- newMatchedItems,
- };
-}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 07af32bb8..baafc63dd 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -130,7 +130,7 @@ import {
PendingOperationInfo,
PendingOperationsResponse,
PendingOperationType,
-} from "./types/pending";
+} from "./types/pendingTypes";
import { WalletNotification, NotificationType } from "./types/notifications";
import {
processPurchaseQueryRefund,
@@ -148,7 +148,7 @@ import {
TransactionsRequest,
TransactionsResponse,
codecForTransactionsRequest,
-} from "./types/transactions";
+} from "./types/transactionsTypes";
import { getTransactions } from "./operations/transactions";
import {
withdrawTestBalance,
@@ -326,7 +326,7 @@ export class Wallet {
} = {},
): Promise<void> {
let done = false;
- const p = new Promise((resolve, reject) => {
+ const p = new Promise<void>((resolve, reject) => {
// Monitor for conditions that means we're done or we
// should quit with an error (due to exceeded retries).
this.addNotificationListener((n) => {