aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-12-16 21:10:57 +0100
committerFlorian Dold <florian.dold@gmail.com>2019-12-16 21:10:57 +0100
commitfb6508de9d71600dbca59cb0e6a4c77e4f3f3ee5 (patch)
tree8f1e9e50574ee6d9f561cb93d2ea29663943672d /src
parentc2ee8fd9ab6754275d7423152681236a46cf36a9 (diff)
downloadwallet-core-fb6508de9d71600dbca59cb0e6a4c77e4f3f3ee5.tar.gz
wallet-core-fb6508de9d71600dbca59cb0e6a4c77e4f3f3ee5.tar.bz2
wallet-core-fb6508de9d71600dbca59cb0e6a4c77e4f3f3ee5.zip
finish refresh correctly, display fees correctly
Diffstat (limited to 'src')
-rw-r--r--src/headless/taler-wallet-cli.ts30
-rw-r--r--src/operations/history.ts69
-rw-r--r--src/operations/pending.ts2
-rw-r--r--src/operations/refresh.ts14
-rw-r--r--src/operations/reserves.ts161
-rw-r--r--src/types/history.ts10
-rw-r--r--src/types/notifications.ts7
-rw-r--r--src/types/pending.ts2
8 files changed, 189 insertions, 106 deletions
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts
index bc83bac2f..610990ae4 100644
--- a/src/headless/taler-wallet-cli.ts
+++ b/src/headless/taler-wallet-cli.ts
@@ -1,17 +1,17 @@
/*
- This file is part of TALER
- (C) 2019 GNUnet e.V.
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems S.A.
- TALER is free software; you can redistribute it and/or modify it under the
+ 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.
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import os = require("os");
@@ -167,7 +167,10 @@ walletCli
});
walletCli
- .subcommand("", "history", { help: "Show wallet event history." })
+ .subcommand("history", "history", { help: "Show wallet event history." })
+ .flag("json", ["--json"], {
+ default: false,
+ })
.maybeOption("from", ["--from"], clk.STRING)
.maybeOption("to", ["--to"], clk.STRING)
.maybeOption("limit", ["--limit"], clk.STRING)
@@ -175,7 +178,17 @@ walletCli
.action(async args => {
await withWallet(args, async wallet => {
const history = await wallet.getHistory();
- console.log(JSON.stringify(history, undefined, 2));
+ if (args.history.json) {
+ console.log(JSON.stringify(history, undefined, 2));
+ } else {
+ for (const h of history.history) {
+ console.log(
+ `event at ${new Date(h.timestamp.t_ms).toISOString()} with type ${h.type}:`,
+ );
+ console.log(JSON.stringify(h, undefined, 2));
+ console.log();
+ }
+ }
});
});
@@ -231,7 +244,8 @@ walletCli
case TalerUriType.TalerWithdraw:
{
const withdrawInfo = await wallet.getWithdrawDetailsForUri(uri);
- const selectedExchange = withdrawInfo.bankWithdrawDetails.suggestedExchange;
+ const selectedExchange =
+ withdrawInfo.bankWithdrawDetails.suggestedExchange;
if (!selectedExchange) {
console.error("no suggested exchange!");
process.exit(1);
diff --git a/src/operations/history.ts b/src/operations/history.ts
index eec398f37..bb57a9c60 100644
--- a/src/operations/history.ts
+++ b/src/operations/history.ts
@@ -61,7 +61,6 @@ function getOrderShortInfo(
};
}
-
async function collectProposalHistory(
tx: TransactionHandle,
history: HistoryEvent[],
@@ -162,6 +161,7 @@ export async function getHistory(
await ws.db.runWithReadTransaction(
[
Stores.currencies,
+ Stores.coins,
Stores.exchanges,
Stores.exchangeUpdatedEvents,
Stores.proposals,
@@ -220,15 +220,22 @@ export async function getHistory(
await collectProposalHistory(tx, history, historyQuery);
- await tx.iter(Stores.payEvents).forEachAsync(async (pe) => {
+ await tx.iter(Stores.payEvents).forEachAsync(async pe => {
const proposal = await tx.get(Stores.proposals, pe.proposalId);
if (!proposal) {
return;
}
+ const purchase = await tx.get(Stores.purchases, pe.proposalId);
+ if (!purchase) {
+ return;
+ }
const orderShortInfo = getOrderShortInfo(proposal);
if (!orderShortInfo) {
return;
}
+ const amountPaidWithFees = Amounts.sum(
+ purchase.payReq.coins.map(x => Amounts.parseOrThrow(x.contribution)),
+ ).amount;
history.push({
type: HistoryEventType.PaymentSent,
eventId: makeEventId(HistoryEventType.PaymentSent, pe.proposalId),
@@ -236,10 +243,12 @@ export async function getHistory(
replay: pe.isReplay,
sessionId: pe.sessionId,
timestamp: pe.timestamp,
+ numCoins: purchase.payReq.coins.length,
+ amountPaidWithFees: Amounts.toString(amountPaidWithFees),
});
});
- await tx.iter(Stores.refreshGroups).forEachAsync(async (rg) => {
+ await tx.iter(Stores.refreshGroups).forEachAsync(async rg => {
if (!rg.timestampFinished) {
return;
}
@@ -251,23 +260,26 @@ export async function getHistory(
for (let i = 0; i < rg.refreshSessionPerCoin.length; i++) {
const session = rg.refreshSessionPerCoin[i];
numInputCoins++;
+ const c = await tx.get(Stores.coins, rg.oldCoinPubs[i]);
+ if (!c) {
+ continue;
+ }
if (session) {
numRefreshedInputCoins++;
amountsRaw.push(session.amountRefreshInput);
+ amountsRaw.push(c.currentAmount);
amountsEffective.push(session.amountRefreshOutput);
numOutputCoins += session.newDenoms.length;
} else {
- const c = await tx.get(Stores.coins, rg.oldCoinPubs[i]);
- if (!c) {
- continue;
- }
amountsRaw.push(c.currentAmount);
}
}
let amountRefreshedRaw = Amounts.sum(amountsRaw).amount;
let amountRefreshedEffective: AmountJson;
if (amountsEffective.length == 0) {
- amountRefreshedEffective = Amounts.getZero(amountRefreshedRaw.currency);
+ amountRefreshedEffective = Amounts.getZero(
+ amountRefreshedRaw.currency,
+ );
} else {
amountRefreshedEffective = Amounts.sum(amountsEffective).amount;
}
@@ -285,7 +297,7 @@ export async function getHistory(
});
});
- tx.iter(Stores.reserveUpdatedEvents).forEachAsync(async (ru) => {
+ tx.iter(Stores.reserveUpdatedEvents).forEachAsync(async ru => {
const reserve = await tx.get(Stores.reserves, ru.reservePub);
if (!reserve) {
return;
@@ -295,28 +307,31 @@ export async function getHistory(
reserveCreationDetail = {
type: ReserveType.TalerBankWithdraw,
bankUrl: reserve.bankWithdrawStatusUrl,
- }
+ };
} else {
reserveCreationDetail = {
type: ReserveType.Manual,
- }
+ };
}
history.push({
type: HistoryEventType.ReserveBalanceUpdated,
- eventId: makeEventId(HistoryEventType.ReserveBalanceUpdated, ru.reserveUpdateId),
+ eventId: makeEventId(
+ HistoryEventType.ReserveBalanceUpdated,
+ ru.reserveUpdateId,
+ ),
amountExpected: ru.amountExpected,
amountReserveBalance: ru.amountReserveBalance,
- timestamp: reserve.timestampCreated,
+ timestamp: ru.timestamp,
newHistoryTransactions: ru.newHistoryTransactions,
reserveShortInfo: {
exchangeBaseUrl: reserve.exchangeBaseUrl,
reserveCreationDetail,
reservePub: reserve.reservePub,
- }
+ },
});
});
- tx.iter(Stores.tips).forEach((tip) => {
+ tx.iter(Stores.tips).forEach(tip => {
if (tip.acceptedTimestamp) {
history.push({
type: HistoryEventType.TipAccepted,
@@ -328,7 +343,7 @@ export async function getHistory(
}
});
- tx.iter(Stores.refundEvents).forEachAsync(async (re) => {
+ tx.iter(Stores.refundEvents).forEachAsync(async re => {
const proposal = await tx.get(Stores.proposals, re.proposalId);
if (!proposal) {
return;
@@ -341,7 +356,9 @@ export async function getHistory(
if (!orderShortInfo) {
return;
}
- const purchaseAmount = Amounts.parseOrThrow(purchase.contractTerms.amount);
+ const purchaseAmount = Amounts.parseOrThrow(
+ purchase.contractTerms.amount,
+ );
let amountRefundedRaw = Amounts.getZero(purchaseAmount.currency);
let amountRefundedInvalid = Amounts.getZero(purchaseAmount.currency);
let amountRefundedEffective = Amounts.getZero(purchaseAmount.currency);
@@ -352,9 +369,16 @@ export async function getHistory(
}
const refundAmount = Amounts.parseOrThrow(r.perm.refund_amount);
const refundFee = Amounts.parseOrThrow(r.perm.refund_fee);
- amountRefundedRaw = Amounts.add(amountRefundedRaw, refundAmount).amount;
- amountRefundedEffective = Amounts.add(amountRefundedEffective, refundAmount).amount;
- amountRefundedEffective = Amounts.sub(amountRefundedEffective, refundFee).amount;
+ amountRefundedRaw = Amounts.add(amountRefundedRaw, refundAmount)
+ .amount;
+ amountRefundedEffective = Amounts.add(
+ amountRefundedEffective,
+ refundAmount,
+ ).amount;
+ amountRefundedEffective = Amounts.sub(
+ amountRefundedEffective,
+ refundFee,
+ ).amount;
});
Object.keys(purchase.refundState.refundsFailed).forEach((x, i) => {
const r = purchase.refundState.refundsFailed[x];
@@ -365,7 +389,10 @@ export async function getHistory(
const refundFee = Amounts.parseOrThrow(r.perm.refund_fee);
amountRefundedRaw = Amounts.add(amountRefundedRaw, ra).amount;
amountRefundedInvalid = Amounts.add(amountRefundedInvalid, ra).amount;
- amountRefundedEffective = Amounts.sub(amountRefundedEffective, refundFee).amount;
+ amountRefundedEffective = Amounts.sub(
+ amountRefundedEffective,
+ refundFee,
+ ).amount;
});
history.push({
type: HistoryEventType.Refund,
diff --git a/src/operations/pending.ts b/src/operations/pending.ts
index ffa23f101..360180854 100644
--- a/src/operations/pending.ts
+++ b/src/operations/pending.ts
@@ -224,6 +224,8 @@ async function gatherRefreshPending(
type: PendingOperationType.Refresh,
givesLifeness: true,
refreshGroupId: r.refreshGroupId,
+ finishedPerCoin: r.finishedPerCoin,
+ retryInfo: r.retryInfo,
});
});
}
diff --git a/src/operations/refresh.ts b/src/operations/refresh.ts
index f602221af..8390cac54 100644
--- a/src/operations/refresh.ts
+++ b/src/operations/refresh.ts
@@ -144,10 +144,22 @@ async function refreshCreateSession(
return;
}
rg.finishedPerCoin[coinIndex] = true;
+ rg.finishedPerCoin[coinIndex] = true;
+ let allDone = true;
+ for (const f of rg.finishedPerCoin) {
+ if (!f) {
+ allDone = false;
+ break;
+ }
+ }
+ if (allDone) {
+ rg.timestampFinished = getTimestampNow();
+ rg.retryInfo = initRetryInfo(false);
+ }
await tx.put(Stores.refreshGroups, rg);
},
);
- ws.notify({ type: NotificationType.RefreshRefused });
+ ws.notify({ type: NotificationType.RefreshUnwarranted });
return;
}
diff --git a/src/operations/reserves.ts b/src/operations/reserves.ts
index 649bf75f2..7be927824 100644
--- a/src/operations/reserves.ts
+++ b/src/operations/reserves.ts
@@ -34,12 +34,14 @@ import {
updateRetryInfoTimeout,
ReserveUpdatedEventRecord,
} from "../types/dbTypes";
-import {
- TransactionAbort,
-} from "../util/query";
+import { TransactionAbort } from "../util/query";
import { Logger } from "../util/logging";
import * as Amounts from "../util/amounts";
-import { updateExchangeFromUrl, getExchangeTrust, getExchangePaytoUri } from "./exchanges";
+import {
+ updateExchangeFromUrl,
+ getExchangeTrust,
+ getExchangePaytoUri,
+} from "./exchanges";
import { WithdrawOperationStatusResponse } from "../types/talerTypes";
import { assertUnreachable } from "../util/assertUnreachable";
import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
@@ -49,7 +51,10 @@ import {
processWithdrawSession,
getBankWithdrawalInfo,
} from "./withdraw";
-import { guardOperationException, OperationFailedAndReportedError } from "./errors";
+import {
+ guardOperationException,
+ OperationFailedAndReportedError,
+} from "./errors";
import { NotificationType } from "../types/notifications";
import { codecForReserveStatus } from "../types/ReserveStatus";
@@ -206,7 +211,6 @@ export async function processReserve(
});
}
-
async function registerReserveWithBank(
ws: InternalWalletState,
reservePub: string,
@@ -231,7 +235,6 @@ async function registerReserveWithBank(
reserve_pub: reservePub,
selected_exchange: reserve.exchangeWire,
});
- console.log("got response", bankResp);
await ws.db.mutate(Stores.reserves, reservePub, r => {
switch (r.reserveStatus) {
case ReserveRecordStatus.REGISTERING_BANK:
@@ -245,7 +248,7 @@ async function registerReserveWithBank(
r.retryInfo = initRetryInfo();
return r;
});
- ws.notify( { type: NotificationType.Wildcard });
+ ws.notify({ type: NotificationType.Wildcard });
return processReserveBankStatus(ws, reservePub);
}
@@ -282,14 +285,16 @@ async function processReserveBankStatusImpl(
try {
const statusResp = await ws.http.get(bankStatusUrl);
if (statusResp.status !== 200) {
- throw Error(`unexpected status ${statusResp.status} for bank status query`);
+ throw Error(
+ `unexpected status ${statusResp.status} for bank status query`,
+ );
}
status = WithdrawOperationStatusResponse.checked(await statusResp.json());
} catch (e) {
throw e;
}
- ws.notify( { type: NotificationType.Wildcard });
+ ws.notify({ type: NotificationType.Wildcard });
if (status.selection_done) {
if (reserve.reserveStatus === ReserveRecordStatus.REGISTERING_BANK) {
@@ -330,7 +335,7 @@ async function processReserveBankStatusImpl(
});
await incrementReserveRetry(ws, reservePub, undefined);
}
- ws.notify( { type: NotificationType.Wildcard });
+ ws.notify({ type: NotificationType.Wildcard });
}
async function incrementReserveRetry(
@@ -351,7 +356,12 @@ async function incrementReserveRetry(
r.lastError = err;
await tx.put(Stores.reserves, r);
});
- ws.notify({ type: NotificationType.ReserveOperationError });
+ if (err) {
+ ws.notify({
+ type: NotificationType.ReserveOperationError,
+ operationError: err,
+ });
+ }
}
/**
@@ -386,7 +396,7 @@ async function updateReserve(
return;
}
if (resp.status !== 200) {
- throw Error(`unexpected status code ${resp.status} for reserve/status`)
+ throw Error(`unexpected status code ${resp.status} for reserve/status`);
}
} catch (e) {
const m = e.message;
@@ -400,68 +410,73 @@ async function updateReserve(
const respJson = await resp.json();
const reserveInfo = codecForReserveStatus.decode(respJson);
const balance = Amounts.parseOrThrow(reserveInfo.balance);
- await ws.db.runWithWriteTransaction([Stores.reserves, Stores.reserveUpdatedEvents], async (tx) => {
- const r = await tx.get(Stores.reserves, reservePub);
- if (!r) {
- return;
- }
- if (r.reserveStatus !== ReserveRecordStatus.QUERYING_STATUS) {
- return;
- }
-
- const newHistoryTransactions = reserveInfo.history.slice(r.reserveTransactions.length);
-
- const reserveUpdateId = encodeCrock(getRandomBytes(32));
-
- // FIXME: check / compare history!
- if (!r.lastSuccessfulStatusQuery) {
- // FIXME: check if this matches initial expectations
- r.amountWithdrawRemaining = balance;
- const reserveUpdate: ReserveUpdatedEventRecord = {
- reservePub: r.reservePub,
- timestamp: getTimestampNow(),
- amountReserveBalance: Amounts.toString(balance),
- amountExpected: Amounts.toString(reserve.amountInitiallyRequested),
- newHistoryTransactions,
- reserveUpdateId,
- };
- await tx.put(Stores.reserveUpdatedEvents, reserveUpdate);
- } else {
- const expectedBalance = Amounts.sub(
- r.amountWithdrawAllocated,
- r.amountWithdrawCompleted,
- );
- const cmp = Amounts.cmp(balance, expectedBalance.amount);
- if (cmp == 0) {
- // Nothing changed.
+ await ws.db.runWithWriteTransaction(
+ [Stores.reserves, Stores.reserveUpdatedEvents],
+ async tx => {
+ const r = await tx.get(Stores.reserves, reservePub);
+ if (!r) {
+ return;
+ }
+ if (r.reserveStatus !== ReserveRecordStatus.QUERYING_STATUS) {
return;
}
- if (cmp > 0) {
- const extra = Amounts.sub(balance, expectedBalance.amount).amount;
- r.amountWithdrawRemaining = Amounts.add(
- r.amountWithdrawRemaining,
- extra,
- ).amount;
+
+ const newHistoryTransactions = reserveInfo.history.slice(
+ r.reserveTransactions.length,
+ );
+
+ const reserveUpdateId = encodeCrock(getRandomBytes(32));
+
+ // FIXME: check / compare history!
+ if (!r.lastSuccessfulStatusQuery) {
+ // FIXME: check if this matches initial expectations
+ r.amountWithdrawRemaining = balance;
+ const reserveUpdate: ReserveUpdatedEventRecord = {
+ reservePub: r.reservePub,
+ timestamp: getTimestampNow(),
+ amountReserveBalance: Amounts.toString(balance),
+ amountExpected: Amounts.toString(reserve.amountInitiallyRequested),
+ newHistoryTransactions,
+ reserveUpdateId,
+ };
+ await tx.put(Stores.reserveUpdatedEvents, reserveUpdate);
} else {
- // We're missing some money.
+ const expectedBalance = Amounts.sub(
+ r.amountWithdrawAllocated,
+ r.amountWithdrawCompleted,
+ );
+ const cmp = Amounts.cmp(balance, expectedBalance.amount);
+ if (cmp == 0) {
+ // Nothing changed.
+ return;
+ }
+ if (cmp > 0) {
+ const extra = Amounts.sub(balance, expectedBalance.amount).amount;
+ r.amountWithdrawRemaining = Amounts.add(
+ r.amountWithdrawRemaining,
+ extra,
+ ).amount;
+ } else {
+ // We're missing some money.
+ }
+ const reserveUpdate: ReserveUpdatedEventRecord = {
+ reservePub: r.reservePub,
+ timestamp: getTimestampNow(),
+ amountReserveBalance: Amounts.toString(balance),
+ amountExpected: Amounts.toString(expectedBalance.amount),
+ newHistoryTransactions,
+ reserveUpdateId,
+ };
+ await tx.put(Stores.reserveUpdatedEvents, reserveUpdate);
}
- const reserveUpdate: ReserveUpdatedEventRecord = {
- reservePub: r.reservePub,
- timestamp: getTimestampNow(),
- amountReserveBalance: Amounts.toString(balance),
- amountExpected: Amounts.toString(expectedBalance.amount),
- newHistoryTransactions,
- reserveUpdateId,
- };
- await tx.put(Stores.reserveUpdatedEvents, reserveUpdate);
- }
- r.lastSuccessfulStatusQuery = getTimestampNow();
- r.reserveStatus = ReserveRecordStatus.WITHDRAWING;
- r.retryInfo = initRetryInfo();
- r.reserveTransactions = reserveInfo.history;
- await tx.put(Stores.reserves, r);
- });
- ws.notify( { type: NotificationType.ReserveUpdated });
+ r.lastSuccessfulStatusQuery = getTimestampNow();
+ r.reserveStatus = ReserveRecordStatus.WITHDRAWING;
+ r.retryInfo = initRetryInfo();
+ r.reserveTransactions = reserveInfo.history;
+ await tx.put(Stores.reserves, r);
+ },
+ );
+ ws.notify({ type: NotificationType.ReserveUpdated });
}
async function processReserveImpl(
@@ -655,8 +670,6 @@ async function depleteReserve(
}
}
-
-
export async function createTalerWithdrawReserve(
ws: InternalWalletState,
talerWithdrawUri: string,
@@ -683,4 +696,4 @@ export async function createTalerWithdrawReserve(
reservePub: reserve.reservePub,
confirmTransferUrl: withdrawInfo.confirmTransferUrl,
};
-} \ No newline at end of file
+}
diff --git a/src/types/history.ts b/src/types/history.ts
index 8b46276be..aa35ab962 100644
--- a/src/types/history.ts
+++ b/src/types/history.ts
@@ -486,6 +486,16 @@ export interface HistoryPaymentSent {
replay: boolean;
/**
+ * Number of coins that were involved in the payment.
+ */
+ numCoins: number;
+
+ /**
+ * Amount that was paid, including deposit and wire fees.
+ */
+ amountPaidWithFees: string;
+
+ /**
* Session ID that the payment was (re-)submitted under.
*/
sessionId: string | undefined;
diff --git a/src/types/notifications.ts b/src/types/notifications.ts
index c64d33bfb..30ede151c 100644
--- a/src/types/notifications.ts
+++ b/src/types/notifications.ts
@@ -1,3 +1,5 @@
+import { OperationError } from "./walletTypes";
+
/*
This file is part of GNU Taler
(C) 2019 GNUnet e.V.
@@ -29,7 +31,7 @@ export const enum NotificationType {
RefreshRevealed = "refresh-revealed",
RefreshMelted = "refresh-melted",
RefreshStarted = "refresh-started",
- RefreshRefused = "refresh-refused",
+ RefreshUnwarranted = "refresh-unwarranted",
ReserveUpdated = "reserve-updated",
ReserveConfirmed = "reserve-confirmed",
ReserveDepleted = "reserve-depleted",
@@ -100,7 +102,7 @@ export interface RefreshStartedNotification {
}
export interface RefreshRefusedNotification {
- type: NotificationType.RefreshRefused;
+ type: NotificationType.RefreshUnwarranted;
}
export interface ReserveUpdatedNotification {
@@ -170,6 +172,7 @@ export interface WithdrawOperationErrorNotification {
export interface ReserveOperationErrorNotification {
type: NotificationType.ReserveOperationError;
+ operationError: OperationError;
}
export interface ReserveCreatedNotification {
diff --git a/src/types/pending.ts b/src/types/pending.ts
index 53932e8f3..efb97f536 100644
--- a/src/types/pending.ts
+++ b/src/types/pending.ts
@@ -87,6 +87,8 @@ export interface PendingRefreshOperation {
type: PendingOperationType.Refresh;
lastError?: OperationError;
refreshGroupId: string;
+ finishedPerCoin: boolean[];
+ retryInfo: RetryInfo;
}
export interface PendingProposalDownloadOperation {