diff options
author | Florian Dold <florian@dold.me> | 2020-12-16 17:59:04 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2020-12-16 17:59:04 +0100 |
commit | bafb52edff4d56bcb9e3c3d0a260f507c517b08c (patch) | |
tree | 09b484a8cedc9893f5ea7593a98fadde075eafa3 /packages/taler-wallet-core/src/util | |
parent | c09c5bbe625566fc61c811160d2ccdab263327fa (diff) | |
download | wallet-core-bafb52edff4d56bcb9e3c3d0a260f507c517b08c.tar.xz |
don't store reserve history anymore, adjust withdrawal implementation accordingly
Diffstat (limited to 'packages/taler-wallet-core/src/util')
3 files changed, 1 insertions, 649 deletions
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, - }; -} |