From 04a60770ee6a282f4833b8bfe72525ea36b8e295 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 13 Dec 2019 13:10:20 +0100 Subject: new history schema --- src/db.ts | 10 +- src/operations/history.ts | 165 +---------- src/types/dbTypes.ts | 8 - src/types/history.ts | 693 ++++++++++++++++++++++++++++++++++++++++++++-- src/webex/pages/popup.tsx | 4 +- src/webex/wxBackend.ts | 3 +- 6 files changed, 692 insertions(+), 191 deletions(-) diff --git a/src/db.ts b/src/db.ts index 0a07171ba..e1c5c8f85 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,8 +1,16 @@ -import { Stores, WALLET_DB_VERSION } from "./types/dbTypes"; +import { Stores } from "./types/dbTypes"; import { openDatabase, Database } from "./util/query"; const TALER_DB_NAME = "taler"; +/** + * Current database version, should be incremented + * each time we do incompatible schema changes on the database. + * In the future we might consider adding migration functions for + * each version increment. + */ +export const WALLET_DB_VERSION = 28; + /** * Return a promise that resolves * to the taler wallet db. diff --git a/src/operations/history.ts b/src/operations/history.ts index b8d756cc6..64f5b21cc 100644 --- a/src/operations/history.ts +++ b/src/operations/history.ts @@ -17,12 +17,11 @@ /** * Imports. */ -import { Database } from "../util/query"; import { InternalWalletState } from "./state"; import { Stores, TipRecord } from "../types/dbTypes"; import * as Amounts from "../util/amounts"; import { AmountJson } from "../util/amounts"; -import { HistoryQuery, HistoryEvent } from "../types/history"; +import { HistoryQuery, HistoryEvent, HistoryEventType } from "../types/history"; /** * Retrive the full event history for this wallet. @@ -52,166 +51,8 @@ export async function getHistory( Stores.withdrawalSession, ], async tx => { - await tx.iter(Stores.proposals).forEach(p => { - history.push({ - detail: {}, - timestamp: p.timestamp, - type: "claim-order", - explicit: false, - }); - }); - - await tx.iter(Stores.withdrawalSession).forEach(w => { - history.push({ - detail: { - withdrawalAmount: w.rawWithdrawalAmount, - }, - timestamp: w.startTimestamp, - type: "withdraw-started", - explicit: false, - }); - if (w.finishTimestamp) { - history.push({ - detail: { - withdrawalAmount: w.rawWithdrawalAmount, - }, - timestamp: w.finishTimestamp, - type: "withdraw-finished", - explicit: false, - }); - } - }); - - await tx.iter(Stores.purchases).forEach(p => { - history.push({ - detail: { - amount: p.contractTerms.amount, - contractTermsHash: p.contractTermsHash, - fulfillmentUrl: p.contractTerms.fulfillment_url, - merchantName: p.contractTerms.merchant.name, - }, - timestamp: p.acceptTimestamp, - type: "pay-started", - explicit: false, - }); - if (p.firstSuccessfulPayTimestamp) { - history.push({ - detail: { - amount: p.contractTerms.amount, - contractTermsHash: p.contractTermsHash, - fulfillmentUrl: p.contractTerms.fulfillment_url, - merchantName: p.contractTerms.merchant.name, - }, - timestamp: p.firstSuccessfulPayTimestamp, - type: "pay-finished", - explicit: false, - }); - } - if (p.lastRefundStatusTimestamp) { - const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount); - const amountsPending = Object.keys(p.refundsPending).map(x => - Amounts.parseOrThrow(p.refundsPending[x].refund_amount), - ); - const amountsDone = Object.keys(p.refundsDone).map(x => - Amounts.parseOrThrow(p.refundsDone[x].refund_amount), - ); - const amounts: AmountJson[] = amountsPending.concat(amountsDone); - const amount = Amounts.add( - Amounts.getZero(contractAmount.currency), - ...amounts, - ).amount; - - history.push({ - detail: { - contractTermsHash: p.contractTermsHash, - fulfillmentUrl: p.contractTerms.fulfillment_url, - merchantName: p.contractTerms.merchant.name, - refundAmount: amount, - }, - timestamp: p.lastRefundStatusTimestamp, - type: "refund", - explicit: false, - }); - } - }); - - await tx.iter(Stores.reserves).forEach(r => { - const reserveType = r.bankWithdrawStatusUrl ? "taler-bank" : "manual"; - history.push({ - detail: { - exchangeBaseUrl: r.exchangeBaseUrl, - requestedAmount: Amounts.toString(r.initiallyRequestedAmount), - reservePub: r.reservePub, - reserveType, - bankWithdrawStatusUrl: r.bankWithdrawStatusUrl, - }, - timestamp: r.created, - type: "reserve-created", - explicit: false, - }); - if (r.timestampConfirmed) { - history.push({ - detail: { - exchangeBaseUrl: r.exchangeBaseUrl, - requestedAmount: Amounts.toString(r.initiallyRequestedAmount), - reservePub: r.reservePub, - reserveType, - bankWithdrawStatusUrl: r.bankWithdrawStatusUrl, - }, - timestamp: r.created, - type: "reserve-confirmed", - explicit: false, - }); - } - }); - - await tx.iter(Stores.tips).forEach(tip => { - history.push({ - detail: { - accepted: tip.accepted, - amount: tip.amount, - merchantBaseUrl: tip.merchantBaseUrl, - tipId: tip.merchantTipId, - }, - timestamp: tip.createdTimestamp, - explicit: false, - type: "tip", - }); - }); - - await tx.iter(Stores.exchanges).forEach(exchange => { - history.push({ - type: "exchange-added", - explicit: false, - timestamp: exchange.timestampAdded, - detail: { - exchangeBaseUrl: exchange.baseUrl, - }, - }); - }); - - await tx.iter(Stores.refresh).forEach((r) => { - history.push({ - type: "refresh-started", - explicit: false, - timestamp: r.created, - detail: { - refreshSessionId: r.refreshSessionId, - }, - }); - if (r.finishedTimestamp) { - history.push({ - type: "refresh-finished", - explicit: false, - timestamp: r.finishedTimestamp, - detail: { - refreshSessionId: r.refreshSessionId, - }, - }); - } - - }); - }, + // FIXME: implement new history schema!! + } ); history.sort((h1, h2) => Math.sign(h1.timestamp.t_ms - h2.timestamp.t_ms)); diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts index ce2bb4109..6a00a497d 100644 --- a/src/types/dbTypes.ts +++ b/src/types/dbTypes.ts @@ -43,14 +43,6 @@ import { getTimestampNow, } from "./walletTypes"; -/** - * Current database version, should be incremented - * each time we do incompatible schema changes on the database. - * In the future we might consider adding migration functions for - * each version increment. - */ -export const WALLET_DB_VERSION = 28; - export enum ReserveRecordStatus { /** * Waiting for manual confirmation. diff --git a/src/types/history.ts b/src/types/history.ts index e925b0ffe..0adc27d56 100644 --- a/src/types/history.ts +++ b/src/types/history.ts @@ -21,38 +21,697 @@ import { Timestamp } from "./walletTypes"; */ /** - * Activity history record. + * Type tags for the history event types. */ -export interface HistoryEvent { +export const enum HistoryEventType { + AuditorComplaintSent = "auditor-complained-sent", + AuditorComplaintProcessed = "auditor-complaint-processed", + AuditorTrustAdded = "auditor-trust-added", + AuditorTrustRemoved = "auditor-trust-removed", + ExchangeAdded = "exchange-added", + ExchangeTermsAccepted = "exchange-terms-accepted", + ExchangePolicyChanged = "exchange-policy-changed", + ExchangeTrustAdded = "exchange-trust-added", + ExchangeTrustRemoved = "exchange-trust-removed", + ExchangeUpdated = "exchange-updated", + FundsDepositedToSelf = "funds-deposited-to-self", + FundsRecouped = "funds-recouped", + OrderAccepted = "order-accepted", + OrderRedirected = "order-redirected", + OrderRefused = "order-refused", + PaymentAborted = "payment-aborted", + PaymentSent = "payment-sent", + Refreshed = "refreshed", + Refund = "refund", + ReserveBalanceUpdated = "reserve-balance-updated", + ReserveWithdrawAllocated = "reserve-withdraw-allocated", + ReserveConfirmed = "reserve-confirmed", + ReserveCreated = "reserve-created", + TipAccepted = "tip-accepted", + TipDeclined = "tip-declined", + Withdrawn = "withdrawn", +} + +export const enum ReserveType { + /** + * Manually created. + */ + Manual = "manual", + /** + * Withdrawn from a bank that has "tight" Taler integration + */ + TalerBankWithdraw = "taler-bank-withdraw", +} + +/** + * Short info about a reserve. Enough to display in a list view and + * to query more information from the wallet. + */ +export interface ReserveShortInfo { + /** + * The exchange that the reserve will be at. + */ + exchangeBaseUrl: string; + + /** + * Key to query more details + */ + reservePub: string; + + /** + * Detail about how the reserve has been created. + */ + reserveCreationDetail: ReserveCreationDetail; +} + +export type ReserveCreationDetail = + | { type: ReserveType.Manual } + | { type: ReserveType.TalerBankWithdraw; bankUrl: string }; + +export interface HistoryReserveCreatedEvent { + type: HistoryEventType.ReserveCreated; + /** - * Type of the history event. + * Amount that the should appear in the reserve once its status + * is requested from the exchange. */ - type: string; + expectedAmount: string; /** - * Time when the activity was recorded. + * Condensed information about the reserve. + */ + reserveShortInfo: ReserveShortInfo; +} + +/** + * The user (or some application / test case) or the bank has confirmed that the + * reserve will indeed become available after a while, because the + * funds are in the process of being transfered to the exchange. + */ +export interface HistoryReserveConfirmeEvent { + type: HistoryEventType.ReserveConfirmed; + + /** + * Point in time when the reserve was confirmed. */ timestamp: Timestamp; /** - * Details used when rendering the history record. + * Amount that the should appear in the reserve once its status + * is requested from the exchange. */ - detail: any; + expectedAmount: string; /** - * Set to 'true' if the event has been explicitly created, - * and set to 'false' if the event has been derived from the - * state of the database. + * Condensed information about the reserve. */ - explicit: boolean; + reserveShortInfo: ReserveShortInfo; } +/** + * This event is emitted every time we ask the exchange for the status + * of the reserve, and the status has changed. + */ +export interface HistoryReserveBalanceUpdatedEvent { + type: HistoryEventType.ReserveBalanceUpdated; + + /** + * Point in time when the reserve was confirmed. + */ + timestamp: Timestamp; + + /** + * Unique identifier to query more information about this update. + */ + reserveUpdateId: string; + + /** + * Condensed information about the reserve. + */ + reserveShortInfo: ReserveShortInfo; + + /** + * Amount currently left in the reserve. + */ + amountReserveBalance: string; + + /** + * Amount we expected to be in the reserve at that time, + * considering ongoing withdrawals from that reserve. + */ + amountExpected: string; +} + +/** + * This event is emitted every time we ask the exchange for the status + * of the reserve, and the status has changed. + */ +export interface HistoryReserveWithdrawAllocatedEvent { + type: HistoryEventType.ReserveWithdrawAllocated; + + /** + * Point in time when the reserve was confirmed. + */ + timestamp: Timestamp; + + /** + * Unique identifier to query more information about the withdrawal. + */ + withdrawalSessionId: string; + + /** + * Condensed information about the reserve. + */ + reserveShortInfo: ReserveShortInfo; + + /** + * Amount that has been allocated for withdrawal from + * this reserve. + */ + amountWithdrawAllocated: string; +} + +/** + * History event to indicate that the user has accepted a tip. + */ +export interface HistoryTipAcceptedEvent { + type: HistoryEventType.TipAccepted; + + /** + * Point in time when the tip has been accepted. + */ + timestamp: Timestamp; + + /** + * Unique identifier for the tip to query more information. + */ + tipId: string; + + /** + * Raw amount of the tip, without extra fees that apply. + */ + tipRawAmount: string; + + /** + * Amount that the user effectively adds to their balance when + * the tip is accepted. + */ + tipEffectiveAmount: string; +} + +/** + * History event to indicate that the user has accepted a tip. + */ +export interface HistoryTipDeclinedEvent { + type: HistoryEventType.TipAccepted; + + /** + * Point in time when the tip has been declined. + */ + timestamp: Timestamp; + + /** + * Unique identifier for the tip to query more information. + */ + tipId: string; + + /** + * Raw amount of the tip, without extra fees that apply. + */ + tipRawAmount: string; + + /** + * Amount that the user effectively adds to their balance when + * the tip is accepted. + */ + tipEffectiveAmount: string; +} + +/** + * The wallet has send a complaint (typically with cryptographic evidence of + * something having gone wrong) to the auditor. + */ +export interface HistoryAuditorComplaintSentEvent { + type: HistoryEventType.AuditorComplaintSent; + + auditorComplaintId: string; + + /* FIXME: add fields once this feature is implemented */ +} + +/** + * The wallet has received a response from the auditor to a complaint. + */ +export interface HistoryAuditorComplaintProcessedEvent { + type: HistoryEventType.AuditorComplaintProcessed; + + auditorComplaintId: string; + + /* FIXME: add fields once this feature is implemented */ +} + +/** + * The wallet has added an auditor as a trusted auditor. + */ +export interface HistoryAuditorTrustAddedEvent { + type: HistoryEventType.AuditorTrustAdded; + + /** + * Base URL of the auditor. + */ + auditorBaseUrl: string; + + /** + * If set to true, this auditor hasn't been added by the user, + * but is part of the pre-set trusted auditors in the wallet. + */ + builtIn: boolean; +} + +/** + * The wallet has added an auditor as a trusted auditor. + */ +export interface HistoryAuditorTrustRemovedEvent { + type: HistoryEventType.AuditorTrustRemoved; + + /** + * Base URL of the auditor. + */ + auditorBaseUrl: string; +} + +/** + * An exchange has been added to the wallet. The wallet only + * downloads information about the exchange, but does not necessarily + * trust it yet. + */ +export interface HistoryExchangeAddedEvent { + type: HistoryEventType.ExchangeAdded; + + exchangeBaseUrl: string; + + /** + * Set to true if the exchange was pre-configured + * by the wallet. + */ + builtIn: boolean; +} + +/** + * History event to indicate that the wallet now trusts + * an exchange. + */ +export interface HistoryExchangeTrustAddedEvent { + type: HistoryEventType.ExchangeTrustAdded; + + exchangeBaseUrl: string; + + /** + * Set to true if the exchange was pre-configured + * by the wallet. + */ + builtIn: boolean; +} + +/** + * History event to indicate that the wallet doesn't trust + * an exchange anymore. + */ +export interface HistoryExchangeTrustRemovedEvent { + type: HistoryEventType.ExchangeTrustRemoved; + + exchangeBaseUrl: string; +} + +/** + * This event indicates that the user accepted the terms of service + * of the exchange. + */ +export interface HistoryExchangeTermsAcceptedEvent { + type: HistoryEventType.ExchangeTermsAccepted; + + exchangeBaseUrl: string; + + /** + * Etag that the exchange sent with the terms of service. + * Identifies the version of the terms of service. + */ + termsEtag: string | undefined; +} + +/** + * The exchange has changed the terms of service or privacy + * policy. + */ +export interface HistoryExchangePolicyChangedEvent { + type: HistoryEventType.ExchangePolicyChanged; + + exchangeBaseUrl: string; + + /** + * Etag that the exchange sent with the terms of service. + * Identifies the version of the terms of service. + */ + termsEtag: string | undefined; + + /** + * Etag that the exchange sent with the privacy policy. + * Identifies the version of the privacy policy. + */ + privacyPolicyEtag: string | undefined; +} + +/** + * This history event indicates that the exchange has updated + * the /keys or /wire information. The event is only emitted if + * this information changed since the last time the wallet checked it. + */ +export interface HistoryExchangeUpdatedEvent { + type: HistoryEventType.ExchangeUpdated; + + exchangeBaseUrl: string; +} + +/** + * History event to indicate that the user sent back digital cash from + * their wallet back to their own bank account (basically acting as a merchant). + */ +export interface HistoryFundsDepositedToSelfEvent { + type: HistoryEventType.FundsDepositedToSelf; + + /** + * Amount that got sent back. + */ + amount: string; + + /** + * Account that received the funds. + */ + receiverPaytoUri: string; +} + +/** + * History event to indicate that the exchange marked + * some denominations as "compromised", and the wallet has + * converted funds in these denominations to new funds. + */ +export interface HistoryFundsRecoupedEvent { + type: HistoryEventType.FundsDepositedToSelf; + + exchangeBaseUrl: string; + + /** + * Amount that the wallet managed to recover. + */ + amountRecouped: string; + + /** + * Amount that was lost due to fees. + */ + amountLost: string; +} + +/** + * Condensed information about an order, enough to display in a list view + * and to query more details from the wallet for a detail view. + */ +export interface OrderShortInfo { + /** + * Wallet-internal identifier of the proposal. + */ + proposalId: string; + + /** + * Order ID, uniquely identifies the order within a merchant instance. + */ + orderId: string; + + /** + * Base URL of the merchant. + */ + merchantBaseUrl: string; + + /** + * Amount that must be paid for the contract. + */ + amountRequested: string; + + /** + * Amount that would be subtracted from the wallet when paying, + * includes fees and funds lost due to refreshing or left-over + * amounts too small to refresh. + */ + amountEffective: string; + + /** + * Summary of the proposal, given by the merchant. + */ + summary: string; +} + +/** + * The user has accepted purchasing something. + */ +export interface HistoryOrderAcceptedEvent { + /** + * Type tag. + */ + type: HistoryEventType.OrderAccepted; + + /** + * Condensed info about the order. + */ + orderShortInfo: OrderShortInfo; +} + +/** + * The customer refused to pay. + */ +export interface HistoryOrderRefusedEvent { + /** + * Type tag. + */ + type: HistoryEventType.OrderRefused; + + /** + * Condensed info about the order. + */ + orderShortInfo: OrderShortInfo; +} + +/** + * The wallet has claimed an order. + */ +export interface HistoryOrderRedirectedEvent { + /** + * Type tag. + */ + type: HistoryEventType.OrderRedirected; + + /** + * Condensed info about the new order that contains a + * product (identified by the fulfillment URL) that we've already paid for. + */ + newOrderShortInfo: OrderShortInfo; + + /** + * Condensed info about the order that we already paid for. + */ + alreadyPaidOrderShortInfo: OrderShortInfo; +} + +/** + * The user aborted a pending, partially submitted payment. + */ +export interface HistoryPaymentAbortedEvent { + /** + * Type tag. + */ + type: HistoryEventType.PaymentAborted; + + /** + * Condensed info about the order that we already paid for. + */ + orderShortInfo: OrderShortInfo; + + /** + * Amount that was lost due to refund and refreshing fees. + */ + amountLost: string; +} + +/** + * History event to indicate that a payment has been (re-)submitted + * to the merchant. + */ +export interface HistoryPaymentSent { + /** + * Type tag. + */ + type: HistoryEventType.PaymentAborted; + + /** + * Condensed info about the order that we already paid for. + */ + orderShortInfo: OrderShortInfo; -export interface HistoryQuery { /** - * Verbosity of history events. - * Level 0: Only withdraw, pay, tip and refund events. - * Level 1: All events. + * Set to true if the payment has been previously sent + * to the merchant successfully, possibly with a different session ID. */ - level: number; -} \ No newline at end of file + replay: boolean; + + /** + * Session ID that the payment was (re-)submitted under. + */ + sessionId: string | undefined; +} + +/** + * A refund has been applied. + */ +export interface HistoryRefund { + /** + * Type tag. + */ + type: HistoryEventType.Refund; + + timestamp: Timestamp; + + orderShortInfo: OrderShortInfo; + + /** + * Unique identifier for this refund. + * (Identifies multiple refund permissions that were obtained at once.) + */ + refundId: string; + + /** + * Part of the refund that couldn't be applied because + * the refund permissions were expired. + */ + amountRefundedInvalid: string; + + /** + * Amount that has been refunded by the merchant. + */ + amountRefundedRaw: string; + + /** + * Amount will be added to the wallet's balance after fees and refreshing. + */ + amountRefundedEffective: string; +} + +/** + * Reasons for why a coin is being refreshed. + */ +export const enum RefreshReason { + Manual = "manual", + Pay = "pay", + Refund = "refund", + AbortPay = "abort-pay", +} + +/** + * Event to indicate that a refresh operation completed. + */ +export interface HistoryRefreshedEvent { + /** + * Type tag. + */ + type: HistoryEventType.Refreshed; + + /** + * Amount that is now available again because it has + * been refreshed. + */ + amountRefreshed: string; + + /** + * Why was the refresh done? + */ + refreshReason: RefreshReason; + + /** + * Refresh session ID, to find out more details. + */ + refreshSessionId: string; +} + +/** + * A withdrawal has completed. + */ +export interface HistoryWithdrawnEvent { + type: HistoryEventType.Withdrawn; + + /** + * Exchange that was withdrawn from. + */ + exchangeBaseUrl: string; + + /** + * Unique identifier for the withdrawal session, can be used to + * query more detailed information from the wallet. + */ + withdrawSessionId: string; + + /** + * Amount that has been subtracted from the reserve's balance + * for this withdrawal. + */ + amountWithdrawnRaw: string; + + /** + * Amount that actually was added to the wallet's balance. + */ + amountWithdrawnEffective: string; +} + +/** + * Common fields that all history events need to have. + */ +export interface HistoryEventBase { + type: HistoryEventType; + + /** + * Main timestamp of the history event. + */ + timestamp: Timestamp; +} + +/** + * Union of all history detail types, discriminated by their type. + */ +export type HistoryEvent = HistoryEventBase & + ( + | HistoryAuditorComplaintSentEvent + | HistoryAuditorComplaintProcessedEvent + | HistoryAuditorTrustAddedEvent + | HistoryAuditorTrustRemovedEvent + | HistoryExchangeAddedEvent + | HistoryExchangeTermsAcceptedEvent + | HistoryExchangePolicyChangedEvent + | HistoryExchangeTrustAddedEvent + | HistoryExchangeTrustRemovedEvent + | HistoryExchangeUpdatedEvent + | HistoryFundsDepositedToSelfEvent + | HistoryFundsRecoupedEvent + | HistoryOrderAcceptedEvent + | HistoryOrderRedirectedEvent + | HistoryOrderRefusedEvent + | HistoryPaymentAbortedEvent + | HistoryPaymentSent + | HistoryRefreshedEvent + | HistoryRefund + | HistoryReserveBalanceUpdatedEvent + | HistoryReserveConfirmeEvent + | HistoryReserveCreatedEvent + | HistoryTipAcceptedEvent + | HistoryTipDeclinedEvent + | HistoryWithdrawnEvent + ); + +export interface HistoryQuery { + // TBD +} diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx index 3a2856d64..b26e86e65 100644 --- a/src/webex/pages/popup.tsx +++ b/src/webex/pages/popup.tsx @@ -325,9 +325,10 @@ class WalletBalanceView extends React.Component { } function formatHistoryItem(historyItem: HistoryEvent) { - const d = historyItem.detail; + const d = historyItem; console.log("hist item", historyItem); switch (historyItem.type) { + /* case "create-reserve": return ( @@ -414,6 +415,7 @@ function formatHistoryItem(historyItem: HistoryEvent) { ); } + */ default: return

{i18n.str`Unknown event (${historyItem.type})`}

; } diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 0a44dc193..d5a08b5d7 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -24,8 +24,7 @@ * Imports. */ import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi"; -import { deleteTalerDatabase, openTalerDatabase } from "../db"; -import { WALLET_DB_VERSION } from "../types/dbTypes"; +import { deleteTalerDatabase, openTalerDatabase, WALLET_DB_VERSION } from "../db"; import { ConfirmReserveRequest, CreateReserveRequest, ReturnCoinsRequest, WalletDiagnostics } from "../types/walletTypes"; import { AmountJson } from "../util/amounts"; import { BrowserHttpLib } from "../util/http"; -- cgit v1.2.3