commit d4442df30a6bafd9c00423888f4edfd88e0e5c90
parent 9686ed3428d64ba8480bf6632fd08adde47cbbe8
Author: Florian Dold <florian@dold.me>
Date: Wed, 17 Jul 2024 21:48:38 +0200
wallet-core: tolerate withdrawal with missing exchange entry
Diffstat:
1 file changed, 77 insertions(+), 78 deletions(-)
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -178,68 +178,8 @@ import { WalletExecutionContext, getDenomInfo } from "./wallet.js";
*/
const logger = new Logger("withdraw.ts");
-/**
- * Update the materialized withdrawal transaction based
- * on the withdrawal group record.
- */
-async function updateWithdrawalTransaction(
- ctx: WithdrawTransactionContext,
- tx: WalletDbReadWriteTransaction<
- [
- "withdrawalGroups",
- "transactionsMeta",
- "operationRetries",
- "exchanges",
- "exchangeDetails",
- ]
- >,
-): Promise<void> {
- const wgRecord = await tx.withdrawalGroups.get(ctx.withdrawalGroupId);
- if (!wgRecord) {
- await tx.transactionsMeta.delete(ctx.transactionId);
- return;
- }
-
- if (
- !wgRecord.instructedAmount ||
- !wgRecord.denomsSel ||
- !wgRecord.exchangeBaseUrl
- ) {
- // withdrawal group is in preparation, nothing to update
- return;
- }
-
- if (wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated) {
- } else if (
- wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankManual
- ) {
- checkDbInvariant(
- wgRecord.instructedAmount !== undefined,
- "manual withdrawal without amount can't be created",
- );
- checkDbInvariant(
- wgRecord.denomsSel !== undefined,
- "manual withdrawal without denoms can't be created",
- );
- } else {
- // FIXME: If this is an orphaned withdrawal for a p2p transaction, we
- // still might want to report the withdrawal.
- return;
- }
- await tx.transactionsMeta.put({
- transactionId: ctx.transactionId,
- status: wgRecord.status,
- timestamp: wgRecord.timestampStart,
- currency: Amounts.currencyOf(wgRecord.instructedAmount),
- exchanges: [wgRecord.exchangeBaseUrl],
- });
-
- // FIXME: Handle orphaned withdrawals where the p2p or recoup tx was deleted?
-}
-
function buildTransactionForBankIntegratedWithdraw(
wg: WithdrawalGroupRecord,
- exchangeDetails: ExchangeWireDetails | undefined,
ort?: OperationRetryRecord,
): TransactionWithdrawal {
if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
@@ -294,14 +234,14 @@ function buildTransactionForBankIntegratedWithdraw(
function buildTransactionForManualWithdraw(
wg: WithdrawalGroupRecord,
- exchangeDetails: ExchangeWireDetails,
+ exchangeDetails: ExchangeWireDetails | undefined,
ort?: OperationRetryRecord,
): TransactionWithdrawal {
if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual)
throw Error("");
const plainPaytoUris =
- exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
+ exchangeDetails?.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
@@ -376,27 +316,27 @@ export class WithdrawTransactionContext implements TransactionContext {
return undefined;
}
const ort = await tx.operationRetries.get(this.taskId);
- const exchangeDetails =
- withdrawalGroupRecord.exchangeBaseUrl === undefined
- ? undefined
- : await getExchangeWireDetailsInTx(
- tx,
- withdrawalGroupRecord.exchangeBaseUrl,
- );
if (
withdrawalGroupRecord.wgInfo.withdrawalType ===
WithdrawalRecordType.BankIntegrated
) {
return buildTransactionForBankIntegratedWithdraw(
withdrawalGroupRecord,
- exchangeDetails,
ort,
);
}
- checkDbInvariant(
- exchangeDetails !== undefined,
- "manual withdrawal without exchange",
- );
+ const exchangeDetails =
+ withdrawalGroupRecord.exchangeBaseUrl === undefined
+ ? undefined
+ : await getExchangeWireDetailsInTx(
+ tx,
+ withdrawalGroupRecord.exchangeBaseUrl,
+ );
+ if (!exchangeDetails) {
+ logger.warn(
+ `transaction ${this.transactionId} is a manual withdrawal, but no exchange wire details found`,
+ );
+ }
return buildTransactionForManualWithdraw(
withdrawalGroupRecord,
exchangeDetails,
@@ -405,6 +345,66 @@ export class WithdrawTransactionContext implements TransactionContext {
}
/**
+ * Update the metadata of the transaction in the database.
+ */
+ async updateTransactionMeta(
+ tx: WalletDbReadWriteTransaction<
+ [
+ "withdrawalGroups",
+ "transactionsMeta",
+ "operationRetries",
+ "exchanges",
+ "exchangeDetails",
+ ]
+ >,
+ ): Promise<void> {
+ const ctx = this;
+ const wgRecord = await tx.withdrawalGroups.get(ctx.withdrawalGroupId);
+ if (!wgRecord) {
+ await tx.transactionsMeta.delete(ctx.transactionId);
+ return;
+ }
+
+ if (
+ !wgRecord.instructedAmount ||
+ !wgRecord.denomsSel ||
+ !wgRecord.exchangeBaseUrl
+ ) {
+ // withdrawal group is in preparation, nothing to update
+ return;
+ }
+
+ if (
+ wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated
+ ) {
+ } else if (
+ wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankManual
+ ) {
+ checkDbInvariant(
+ wgRecord.instructedAmount !== undefined,
+ "manual withdrawal without amount can't be created",
+ );
+ checkDbInvariant(
+ wgRecord.denomsSel !== undefined,
+ "manual withdrawal without denoms can't be created",
+ );
+ } else {
+ // FIXME: If this is an orphaned withdrawal for a p2p transaction, we
+ // still might want to report the withdrawal.
+ return;
+ }
+ await tx.transactionsMeta.put({
+ transactionId: ctx.transactionId,
+ status: wgRecord.status,
+ timestamp: wgRecord.timestampStart,
+ currency: Amounts.currencyOf(wgRecord.instructedAmount),
+ exchanges: [wgRecord.exchangeBaseUrl],
+ });
+
+ // FIXME: Handle orphaned withdrawals where the p2p or recoup tx was deleted?
+ }
+
+ /**
* Transition a withdrawal transaction.
* Extra object stores may be accessed during the transition.
*/
@@ -458,11 +458,10 @@ export class WithdrawTransactionContext implements TransactionContext {
return undefined;
}
- // const res = await f(wgRec, tx);
switch (res.type) {
case TransitionResultType.Transition: {
await tx.withdrawalGroups.put(res.rec);
- await updateWithdrawalTransaction(this, tx);
+ await this.updateTransactionMeta(tx);
const newTxState = computeWithdrawalTransactionStatus(res.rec);
return {
oldTxState,
@@ -471,7 +470,7 @@ export class WithdrawTransactionContext implements TransactionContext {
}
case TransitionResultType.Delete:
await tx.withdrawalGroups.delete(this.withdrawalGroupId);
- await updateWithdrawalTransaction(this, tx);
+ await this.updateTransactionMeta(tx);
return {
oldTxState,
newTxState: {
@@ -3129,7 +3128,7 @@ export async function internalCreateWithdrawalGroup(
},
async (tx) => {
const res = await internalPerformCreateWithdrawalGroup(wex, tx, prep);
- await updateWithdrawalTransaction(ctx, tx);
+ await ctx.updateTransactionMeta(tx);
return res;
},
);