From b9e43e652e606706a81f39d4f888477580de79b0 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 8 Sep 2020 19:27:08 +0530 Subject: fix tipping and adjust DB --- .../taler-wallet-core/src/operations/pending.ts | 1 - .../taler-wallet-core/src/operations/reserves.ts | 5 +- packages/taler-wallet-core/src/operations/tip.ts | 131 +++++++++++---------- .../src/operations/transactions.ts | 98 +++++++-------- .../taler-wallet-core/src/operations/withdraw.ts | 9 +- 5 files changed, 118 insertions(+), 126 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 91a55c705..7dda1214d 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -286,7 +286,6 @@ async function gatherWithdrawalPending( givesLifeness: true, numCoinsTotal, numCoinsWithdrawn, - source: wsr.source, withdrawalGroupId: wsr.withdrawalGroupId, lastError: wsr.lastError, retryInfo: wsr.retryInfo, diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index 69942fe94..b4fa3b23e 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -818,10 +818,7 @@ async function depleteReserve( const withdrawalRecord: WithdrawalGroupRecord = { withdrawalGroupId: withdrawalGroupId, exchangeBaseUrl: newReserve.exchangeBaseUrl, - source: { - type: WithdrawalSourceType.Reserve, - reservePub: newReserve.reservePub, - }, + reservePub: newReserve.reservePub, rawWithdrawalAmount: withdrawAmount, timestampStart: getTimestampNow(), retryInfo: initRetryInfo(), diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 6fe374bf0..6ccd262b0 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -31,6 +31,9 @@ import { updateRetryInfoTimeout, WithdrawalSourceType, TipPlanchet, + CoinRecord, + CoinSourceType, + CoinStatus, } from "../types/dbTypes"; import { getExchangeWithdrawalInfo, @@ -40,13 +43,14 @@ import { } from "./withdraw"; import { updateExchangeFromUrl } from "./exchanges"; import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto"; -import { guardOperationException } from "./errors"; +import { guardOperationException, makeErrorDetails } from "./errors"; import { NotificationType } from "../types/notifications"; import { getTimestampNow } from "../util/time"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { URL } from "../util/url"; import { Logger } from "../util/logging"; import { checkDbInvariant } from "../util/invariants"; +import { TalerErrorCode } from "../TalerErrorCode"; const logger = new Logger("operations/tip.ts"); @@ -99,7 +103,7 @@ export async function prepareTip( walletTipId: walletTipId, acceptedTimestamp: undefined, rejectedTimestamp: undefined, - amount, + tipAmountRaw: amount, deadline: tipPickupStatus.expiration, exchangeUrl: tipPickupStatus.exchange_url, merchantBaseUrl: res.merchantBaseUrl, @@ -109,10 +113,10 @@ export async function prepareTip( response: undefined, createdTimestamp: getTimestampNow(), merchantTipId: res.merchantTipId, - totalFees: Amounts.add( + tipAmountEffective: Amounts.sub(amount, Amounts.add( withdrawDetails.overhead, withdrawDetails.withdrawFee, - ).amount, + ).amount).amount, retryInfo: initRetryInfo(), lastError: undefined, denomsSel: denomSelectionInfoToState(selectedDenoms), @@ -122,10 +126,10 @@ export async function prepareTip( const tipStatus: PrepareTipResult = { accepted: !!tipRecord && !!tipRecord.acceptedTimestamp, - amount: Amounts.stringify(tipPickupStatus.tip_amount), + tipAmountRaw: Amounts.stringify(tipPickupStatus.tip_amount), exchangeBaseUrl: tipPickupStatus.exchange_url, expirationTimestamp: tipPickupStatus.expiration, - totalFees: Amounts.stringify(tipRecord.totalFees), + tipAmountEffective: Amounts.stringify(tipRecord.tipAmountEffective), walletTipId: tipRecord.walletTipId, }; @@ -182,13 +186,13 @@ async function resetTipRetry( async function processTipImpl( ws: InternalWalletState, - tipId: string, + walletTipId: string, forceNow: boolean, ): Promise { if (forceNow) { - await resetTipRetry(ws, tipId); + await resetTipRetry(ws, walletTipId); } - let tipRecord = await ws.db.get(Stores.tips, tipId); + let tipRecord = await ws.db.get(Stores.tips, walletTipId); if (!tipRecord) { return; } @@ -216,7 +220,7 @@ async function processTipImpl( planchets.push(r); } } - await ws.db.mutate(Stores.tips, tipId, (r) => { + await ws.db.mutate(Stores.tips, walletTipId, (r) => { if (!r.planchets) { r.planchets = planchets; } @@ -224,7 +228,7 @@ async function processTipImpl( }); } - tipRecord = await ws.db.get(Stores.tips, tipId); + tipRecord = await ws.db.get(Stores.tips, walletTipId); checkDbInvariant(!!tipRecord, "tip record should be in database"); checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets"); @@ -246,55 +250,68 @@ async function processTipImpl( codecForTipResponse(), ); - if (response.reserve_sigs.length !== tipRecord.planchets.length) { + if (response.blind_sigs.length !== tipRecord.planchets.length) { throw Error("number of tip responses does not match requested planchets"); } - const withdrawalGroupId = encodeCrock(getRandomBytes(32)); - const planchets: PlanchetRecord[] = []; - - for (let i = 0; i < tipRecord.planchets.length; i++) { - const tipPlanchet = tipRecord.planchets[i]; - const coinEvHash = await ws.cryptoApi.hashEncoded(tipPlanchet.coinEv); - const planchet: PlanchetRecord = { - blindingKey: tipPlanchet.blindingKey, - coinEv: tipPlanchet.coinEv, - coinPriv: tipPlanchet.coinPriv, - coinPub: tipPlanchet.coinPub, - coinValue: tipPlanchet.coinValue, - denomPub: tipPlanchet.denomPub, - denomPubHash: tipPlanchet.denomPubHash, - reservePub: response.reserve_pub, - withdrawSig: response.reserve_sigs[i].reserve_sig, - isFromTip: true, - coinEvHash, - coinIdx: i, - withdrawalDone: false, - withdrawalGroupId: withdrawalGroupId, - lastError: undefined, - }; - planchets.push(planchet); - } + const newCoinRecords: CoinRecord[] = []; - const withdrawalGroup: WithdrawalGroupRecord = { - exchangeBaseUrl: tipRecord.exchangeUrl, - source: { - type: WithdrawalSourceType.Tip, - tipId: tipRecord.walletTipId, - }, - timestampStart: getTimestampNow(), - withdrawalGroupId: withdrawalGroupId, - rawWithdrawalAmount: tipRecord.amount, - retryInfo: initRetryInfo(), - timestampFinish: undefined, - lastError: undefined, - denomsSel: tipRecord.denomsSel, - }; + for (let i = 0; i < response.blind_sigs.length; i++) { + const blindedSig = response.blind_sigs[i].blind_sig; + + const planchet = tipRecord.planchets[i]; + + const denomSig = await ws.cryptoApi.rsaUnblind( + blindedSig, + planchet.blindingKey, + planchet.denomPub, + ); + + const isValid = await ws.cryptoApi.rsaVerify( + planchet.coinPub, + denomSig, + planchet.denomPub, + ); + + if (!isValid) { + await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => { + const tipRecord = await tx.get(Stores.tips, walletTipId); + if (!tipRecord) { + return; + } + tipRecord.lastError = makeErrorDetails( + TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID, + "invalid signature from the exchange (via merchant tip) after unblinding", + {}, + ); + await tx.put(Stores.tips, tipRecord); + }); + return; + } + + newCoinRecords.push({ + blindingKey: planchet.blindingKey, + coinPriv: planchet.coinPriv, + coinPub: planchet.coinPub, + coinSource: { + type: CoinSourceType.Tip, + coinIndex: i, + walletTipId: walletTipId, + }, + currentAmount: planchet.coinValue, + denomPub: planchet.denomPub, + denomPubHash: planchet.denomPubHash, + denomSig: denomSig, + exchangeBaseUrl: tipRecord.exchangeUrl, + status: CoinStatus.Fresh, + suspended: false, + }); + } await ws.db.runWithWriteTransaction( - [Stores.tips, Stores.withdrawalGroups], + [Stores.coins, Stores.tips, Stores.withdrawalGroups], async (tx) => { - const tr = await tx.get(Stores.tips, tipId); + const tr = await tx.get(Stores.tips, walletTipId); if (!tr) { return; } @@ -303,16 +320,12 @@ async function processTipImpl( } tr.pickedUp = true; tr.retryInfo = initRetryInfo(false); - await tx.put(Stores.tips, tr); - await tx.put(Stores.withdrawalGroups, withdrawalGroup); - for (const p of planchets) { - await tx.put(Stores.planchets, p); + for (const cr of newCoinRecords) { + await tx.put(Stores.coins, cr); } }, ); - - await processWithdrawGroup(ws, withdrawalGroupId); } export async function acceptTip( diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 7a3228422..b5f77a190 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -116,63 +116,49 @@ export async function getTransactions( return; } - switch (wsr.source.type) { - case WithdrawalSourceType.Reserve: - { - const r = await tx.get(Stores.reserves, wsr.source.reservePub); - if (!r) { - break; - } - let amountRaw: AmountJson | undefined = undefined; - if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) { - amountRaw = r.instructedAmount; - } else { - amountRaw = wsr.denomsSel.totalWithdrawCost; - } - let withdrawalDetails: WithdrawalDetails; - if (r.bankInfo) { - withdrawalDetails = { - type: WithdrawalType.TalerBankIntegrationApi, - confirmed: true, - bankConfirmationUrl: r.bankInfo.confirmUrl, - }; - } else { - const exchange = await tx.get( - Stores.exchanges, - r.exchangeBaseUrl, - ); - if (!exchange) { - // FIXME: report somehow - break; - } - withdrawalDetails = { - type: WithdrawalType.ManualTransfer, - exchangePaytoUris: - exchange.wireInfo?.accounts.map((x) => x.payto_uri) ?? [], - }; - } - transactions.push({ - type: TransactionType.Withdrawal, - amountEffective: Amounts.stringify( - wsr.denomsSel.totalCoinValue, - ), - amountRaw: Amounts.stringify(amountRaw), - withdrawalDetails, - exchangeBaseUrl: wsr.exchangeBaseUrl, - pending: !wsr.timestampFinish, - timestamp: wsr.timestampStart, - transactionId: makeEventId( - TransactionType.Withdrawal, - wsr.withdrawalGroupId, - ), - ...(wsr.lastError ? { error: wsr.lastError } : {}), - }); - } - break; - default: - // Tips are reported via their own event - break; + const r = await tx.get(Stores.reserves, wsr.reservePub); + if (!r) { + return; + } + let amountRaw: AmountJson | undefined = undefined; + if (wsr.withdrawalGroupId === r.initialWithdrawalGroupId) { + amountRaw = r.instructedAmount; + } else { + amountRaw = wsr.denomsSel.totalWithdrawCost; + } + let withdrawalDetails: WithdrawalDetails; + if (r.bankInfo) { + withdrawalDetails = { + type: WithdrawalType.TalerBankIntegrationApi, + confirmed: true, + bankConfirmationUrl: r.bankInfo.confirmUrl, + }; + } else { + const exchange = await tx.get(Stores.exchanges, r.exchangeBaseUrl); + if (!exchange) { + // FIXME: report somehow + return; + } + withdrawalDetails = { + type: WithdrawalType.ManualTransfer, + exchangePaytoUris: + exchange.wireInfo?.accounts.map((x) => x.payto_uri) ?? [], + }; } + transactions.push({ + type: TransactionType.Withdrawal, + amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), + amountRaw: Amounts.stringify(amountRaw), + withdrawalDetails, + exchangeBaseUrl: wsr.exchangeBaseUrl, + pending: !wsr.timestampFinish, + timestamp: wsr.timestampStart, + transactionId: makeEventId( + TransactionType.Withdrawal, + wsr.withdrawalGroupId, + ), + ...(wsr.lastError ? { error: wsr.lastError } : {}), + }); }); // Report pending withdrawals based on reserves that diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 4070e39f4..eec92ba29 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -242,12 +242,9 @@ async function processPlanchetGenerate( if (!denom) { throw Error("invariant violated"); } - if (withdrawalGroup.source.type != WithdrawalSourceType.Reserve) { - throw Error("invariant violated"); - } const reserve = await ws.db.get( Stores.reserves, - withdrawalGroup.source.reservePub, + withdrawalGroup.reservePub, ); if (!reserve) { throw Error("invariant violated"); @@ -420,7 +417,7 @@ async function processPlanchetVerifyAndStoreCoin( if (!isValid) { await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => { - let planchet = await ws.db.getIndexed(Stores.planchets.byGroupAndIndex, [ + let planchet = await tx.getIndexed(Stores.planchets.byGroupAndIndex, [ withdrawalGroupId, coinIdx, ]); @@ -700,7 +697,7 @@ async function processWithdrawGroupImpl( if (finishedForFirstTime) { ws.notify({ type: NotificationType.WithdrawGroupFinished, - withdrawalSource: withdrawalGroup.source, + reservePub: withdrawalGroup.reservePub, }); } } -- cgit v1.2.3