From 54f0c82999833132baf83995526025ac56d6fe06 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 19 Jun 2023 16:03:06 +0200 Subject: wallet-core: fix peer-(push,pull)-debit withdrawal states --- .../taler-wallet-core/src/operations/withdraw.ts | 191 +++++++++++++++------ 1 file changed, 139 insertions(+), 52 deletions(-) (limited to 'packages/taler-wallet-core/src/operations/withdraw.ts') diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 26149bd06..88389fd99 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -109,7 +109,11 @@ import { checkLogicInvariant, InvariantViolatedError, } from "../util/invariants.js"; -import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; +import { + DbAccess, + GetReadOnlyAccess, + GetReadWriteAccess, +} from "../util/query.js"; import { OperationAttemptResult, OperationAttemptResultType, @@ -130,8 +134,13 @@ import { selectForcedWithdrawalDenominations, selectWithdrawalDenominations, } from "../util/coinSelection.js"; -import { PendingTaskType, isWithdrawableDenom } from "../index.js"; import { + ExchangeDetailsRecord, + PendingTaskType, + isWithdrawableDenom, +} from "../index.js"; +import { + TransitionInfo, constructTransactionIdentifier, notifyTransition, stopLongpolling, @@ -2202,15 +2211,19 @@ async function processReserveBankStatus( } } -/** - * Create a withdrawal group. - * - * If a forcedWithdrawalGroupId is given and a - * withdrawal group with this ID already exists, - * the existing one is returned. No conflict checking - * of the other arguments is done in that case. - */ -export async function internalCreateWithdrawalGroup( +export interface PrepareCreateWithdrawalGroupResult { + withdrawalGroup: WithdrawalGroupRecord; + transactionId: string; + creationInfo?: { + isTrusted: boolean; + isAudited: boolean; + amount: AmountJson; + canonExchange: string; + exchangeDetails: ExchangeDetailsRecord; + }; +} + +export async function internalPrepareCreateWithdrawalGroup( ws: InternalWalletState, args: { reserveStatus: WithdrawalGroupStatus; @@ -2222,7 +2235,7 @@ export async function internalCreateWithdrawalGroup( restrictAge?: number; wgInfo: WgInfo; }, -): Promise { +): Promise { const reserveKeyPair = args.reserveKeyPair ?? (await ws.cryptoApi.createEddsaKeypair({})); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); @@ -2240,18 +2253,18 @@ export async function internalCreateWithdrawalGroup( .runReadOnly(async (tx) => { return tx.withdrawalGroups.get(wgId); }); + if (existingWg) { - return existingWg; + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Withdrawal, + withdrawalGroupId: existingWg.withdrawalGroupId, + }); + return { withdrawalGroup: existingWg, transactionId }; } } else { withdrawalGroupId = encodeCrock(getRandomBytes(32)); } - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId, - }); - await updateWithdrawalDenoms(ws, canonExchange); const denoms = await getCandidateWithdrawalDenoms(ws, canonExchange); @@ -2302,8 +2315,112 @@ export async function internalCreateWithdrawalGroup( ws, exchangeInfo.exchange, ); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Withdrawal, + withdrawalGroupId: withdrawalGroup.withdrawalGroupId, + }); - const transitionInfo = await ws.db + return { + withdrawalGroup, + transactionId, + creationInfo: { + isAudited, + isTrusted, + canonExchange, + amount, + exchangeDetails, + }, + }; +} + +export interface PerformCreateWithdrawalGroupResult { + withdrawalGroup: WithdrawalGroupRecord; + transitionInfo: TransitionInfo | undefined; +} + +export async function internalPerformCreateWithdrawalGroup( + ws: InternalWalletState, + tx: GetReadWriteAccess<{ + withdrawalGroups: typeof WalletStoresV1.withdrawalGroups; + reserves: typeof WalletStoresV1.reserves; + exchanges: typeof WalletStoresV1.exchanges; + exchangeTrust: typeof WalletStoresV1.exchangeTrust; + }>, + prep: PrepareCreateWithdrawalGroupResult, +): Promise { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Withdrawal, + withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId, + }); + const { withdrawalGroup } = prep; + if (!prep.creationInfo) { + return { withdrawalGroup, transitionInfo: undefined }; + } + const { isAudited, isTrusted, amount, canonExchange, exchangeDetails } = + prep.creationInfo; + + await tx.withdrawalGroups.add(withdrawalGroup); + await tx.reserves.put({ + reservePub: withdrawalGroup.reservePub, + reservePriv: withdrawalGroup.reservePriv, + }); + + const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl); + if (exchange) { + exchange.lastWithdrawal = TalerPreciseTimestamp.now(); + await tx.exchanges.put(exchange); + } + + if (!isAudited && !isTrusted) { + await tx.exchangeTrust.put({ + currency: amount.currency, + exchangeBaseUrl: canonExchange, + exchangeMasterPub: exchangeDetails.masterPublicKey, + uids: [encodeCrock(getRandomBytes(32))], + }); + } + + const oldTxState = { + major: TransactionMajorState.None, + minor: undefined, + }; + const newTxState = computeWithdrawalTransactionStatus(withdrawalGroup); + const transitionInfo = { + oldTxState, + newTxState, + }; + notifyTransition(ws, transactionId, transitionInfo); + + return { withdrawalGroup, transitionInfo }; +} + +/** + * Create a withdrawal group. + * + * If a forcedWithdrawalGroupId is given and a + * withdrawal group with this ID already exists, + * the existing one is returned. No conflict checking + * of the other arguments is done in that case. + */ +export async function internalCreateWithdrawalGroup( + ws: InternalWalletState, + args: { + reserveStatus: WithdrawalGroupStatus; + amount: AmountJson; + exchangeBaseUrl: string; + forcedWithdrawalGroupId?: string; + forcedDenomSel?: ForcedDenomSel; + reserveKeyPair?: EddsaKeypair; + restrictAge?: number; + wgInfo: WgInfo; + }, +): Promise { + const prep = await internalPrepareCreateWithdrawalGroup(ws, args); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Withdrawal, + withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId, + }); + const res = await ws.db .mktx((x) => [ x.withdrawalGroups, x.reserves, @@ -2312,40 +2429,10 @@ export async function internalCreateWithdrawalGroup( x.exchangeTrust, ]) .runReadWrite(async (tx) => { - await tx.withdrawalGroups.add(withdrawalGroup); - await tx.reserves.put({ - reservePub: withdrawalGroup.reservePub, - reservePriv: withdrawalGroup.reservePriv, - }); - - const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl); - if (exchange) { - exchange.lastWithdrawal = TalerPreciseTimestamp.now(); - await tx.exchanges.put(exchange); - } - - if (!isAudited && !isTrusted) { - await tx.exchangeTrust.put({ - currency: amount.currency, - exchangeBaseUrl: canonExchange, - exchangeMasterPub: exchangeDetails.masterPublicKey, - uids: [encodeCrock(getRandomBytes(32))], - }); - } - - const oldTxState = { - major: TransactionMajorState.None, - }; - const newTxState = computeWithdrawalTransactionStatus(withdrawalGroup); - return { - oldTxState, - newTxState, - }; + return await internalPerformCreateWithdrawalGroup(ws, tx, prep); }); - - notifyTransition(ws, transactionId, transitionInfo); - - return withdrawalGroup; + notifyTransition(ws, transactionId, res.transitionInfo); + return res.withdrawalGroup; } export async function acceptWithdrawalFromUri( -- cgit v1.2.3