aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-util/src/wallet-types.ts10
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts14
-rw-r--r--packages/taler-wallet-core/src/operations/common.ts44
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts13
-rw-r--r--packages/taler-wallet-core/src/operations/recoup.ts4
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts8
-rw-r--r--packages/taler-wallet-core/src/util/retries.ts5
-rw-r--r--packages/taler-wallet-core/src/wallet.ts8
8 files changed, 84 insertions, 22 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 9d95f1ee2..5ff906faa 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -904,6 +904,10 @@ export enum ExchangeEntryStatus {
Ok = "ok",
}
+export interface OperationErrorInfo {
+ error: TalerErrorDetail;
+}
+
// FIXME: This should probably include some error status.
export interface ExchangeListItem {
exchangeBaseUrl: string;
@@ -917,6 +921,12 @@ export interface ExchangeListItem {
* temporarily queried.
*/
permanent: boolean;
+
+ /**
+ * Information about the last error that occured when trying
+ * to update the exchange info.
+ */
+ lastUpdateErrorInfo?: OperationErrorInfo;
}
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
index f0b4ac8c9..6b63c3741 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
@@ -194,12 +194,16 @@ export async function runExchangeManagementTest(
t.assertTrue(
err1.errorDetail.code === TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
);
-
exchangesList = await wallet.client.call(
WalletApiOperation.ListExchanges,
{},
);
- t.assertTrue(exchangesList.exchanges.length === 0);
+ console.log("exchanges list", j2s(exchangesList));
+ t.assertTrue(exchangesList.exchanges.length === 1);
+ t.assertTrue(
+ exchangesList.exchanges[0].lastUpdateErrorInfo?.error.code ===
+ TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+ );
/*
* =========================================================================
@@ -240,7 +244,11 @@ export async function runExchangeManagementTest(
WalletApiOperation.ListExchanges,
{},
);
- t.assertTrue(exchangesList.exchanges.length === 0);
+ t.assertTrue(exchangesList.exchanges.length === 1);
+ t.assertTrue(
+ exchangesList.exchanges[0].lastUpdateErrorInfo?.error.code ===
+ TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
+ );
/*
* =========================================================================
diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts
index e29065390..f35556736 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -28,6 +28,7 @@ import {
ExchangeTosStatus,
j2s,
Logger,
+ OperationErrorInfo,
RefreshReason,
TalerErrorCode,
TalerErrorDetail,
@@ -224,30 +225,37 @@ export async function storeOperationPending(
});
}
-export async function runOperationWithErrorReporting(
+export async function runOperationWithErrorReporting<T1, T2>(
ws: InternalWalletState,
opId: string,
- f: () => Promise<OperationAttemptResult>,
-): Promise<void> {
+ f: () => Promise<OperationAttemptResult<T1, T2>>,
+): Promise<OperationAttemptResult<T1, T2>> {
let maybeError: TalerErrorDetail | undefined;
try {
const resp = await f();
switch (resp.type) {
case OperationAttemptResultType.Error:
- return await storeOperationError(ws, opId, resp.errorDetail);
+ await storeOperationError(ws, opId, resp.errorDetail);
+ return resp;
case OperationAttemptResultType.Finished:
- return await storeOperationFinished(ws, opId);
+ await storeOperationFinished(ws, opId);
+ return resp;
case OperationAttemptResultType.Pending:
- return await storeOperationPending(ws, opId);
+ await storeOperationPending(ws, opId);
+ return resp;
case OperationAttemptResultType.Longpoll:
- break;
+ return resp;
}
} catch (e) {
if (e instanceof TalerError) {
logger.warn("operation processed resulted in error");
logger.warn(`error was: ${j2s(e.errorDetail)}`);
maybeError = e.errorDetail;
- return await storeOperationError(ws, opId, maybeError!);
+ await storeOperationError(ws, opId, maybeError!);
+ return {
+ type: OperationAttemptResultType.Error,
+ errorDetail: e.errorDetail,
+ };
} else if (e instanceof Error) {
// This is a bug, as we expect pending operations to always
// do their own error handling and only throw WALLET_PENDING_OPERATION_FAILED
@@ -261,7 +269,11 @@ export async function runOperationWithErrorReporting(
},
`unexpected exception (message: ${e.message})`,
);
- return await storeOperationError(ws, opId, maybeError);
+ await storeOperationError(ws, opId, maybeError);
+ return {
+ type: OperationAttemptResultType.Error,
+ errorDetail: maybeError,
+ };
} else {
logger.error("Uncaught exception, value is not even an error.");
maybeError = makeErrorDetail(
@@ -269,7 +281,11 @@ export async function runOperationWithErrorReporting(
{},
`unexpected exception (not even an error)`,
);
- return await storeOperationError(ws, opId, maybeError);
+ await storeOperationError(ws, opId, maybeError);
+ return {
+ type: OperationAttemptResultType.Error,
+ errorDetail: maybeError,
+ };
}
}
}
@@ -357,7 +373,13 @@ export function getExchangeTosStatus(
export function makeExchangeListItem(
r: ExchangeRecord,
exchangeDetails: ExchangeDetailsRecord | undefined,
+ lastError: TalerErrorDetail | undefined,
): ExchangeListItem {
+ const lastUpdateErrorInfo: OperationErrorInfo | undefined = lastError
+ ? {
+ error: lastError,
+ }
+ : undefined;
if (!exchangeDetails) {
return {
exchangeBaseUrl: r.baseUrl,
@@ -367,6 +389,7 @@ export function makeExchangeListItem(
exchangeStatus: ExchangeEntryStatus.Unknown,
permanent: r.permanent,
ageRestrictionOptions: [],
+ lastUpdateErrorInfo,
};
}
let exchangeStatus;
@@ -381,5 +404,6 @@ export function makeExchangeListItem(
ageRestrictionOptions: exchangeDetails.ageMask
? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)
: [],
+ lastUpdateErrorInfo,
};
}
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 41e63b956..23ff1479e 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -73,9 +73,11 @@ import {
import {
OperationAttemptResult,
OperationAttemptResultType,
- runOperationHandlerForResult,
+ RetryTags,
+ unwrapOperationHandlerResultOrThrow,
} from "../util/retries.js";
import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js";
+import { runOperationWithErrorReporting } from "./common.js";
import { isWithdrawableDenom } from "./withdraw.js";
const logger = new Logger("exchanges.ts");
@@ -546,8 +548,13 @@ export async function updateExchangeFromUrl(
exchange: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord;
}> {
- return runOperationHandlerForResult(
- await updateExchangeFromUrlHandler(ws, baseUrl, options),
+ const canonUrl = canonicalizeBaseUrl(baseUrl);
+ return unwrapOperationHandlerResultOrThrow(
+ await runOperationWithErrorReporting(
+ ws,
+ RetryTags.forExchangeUpdateFromUrl(canonUrl),
+ () => updateExchangeFromUrlHandler(ws, canonUrl, options),
+ ),
);
}
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts
index c2df6115b..e92c805bd 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -54,7 +54,7 @@ import { checkDbInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js";
import {
OperationAttemptResult,
- runOperationHandlerForResult,
+ unwrapOperationHandlerResultOrThrow,
} from "../util/retries.js";
import { createRefreshGroup, processRefreshGroup } from "./refresh.js";
import { internalCreateWithdrawalGroup } from "./withdraw.js";
@@ -307,7 +307,7 @@ export async function processRecoupGroup(
forceNow?: boolean;
} = {},
): Promise<void> {
- await runOperationHandlerForResult(
+ await unwrapOperationHandlerResultOrThrow(
await processRecoupGroupHandler(ws, recoupGroupId, options),
);
return;
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 0e6bb8339..a9ecdf369 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -1385,6 +1385,7 @@ export async function getWithdrawalDetailsForUri(
x.exchangeDetails,
x.exchangeTos,
x.denominations,
+ x.operationRetries,
])
.runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
@@ -1396,8 +1397,13 @@ export async function getWithdrawalDetailsForUri(
const denominations = await tx.denominations.indexes.byExchangeBaseUrl
.iter(r.baseUrl)
.toArray();
+ const retryRecord = await tx.operationRetries.get(
+ RetryTags.forExchangeUpdate(r),
+ );
if (exchangeDetails && denominations) {
- exchanges.push(makeExchangeListItem(r, exchangeDetails));
+ exchanges.push(
+ makeExchangeListItem(r, exchangeDetails, retryRecord?.lastError),
+ );
}
}
});
diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts
index 5e1089dc5..8861d4d1e 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -176,6 +176,9 @@ export namespace RetryTags {
export function forExchangeUpdate(exch: ExchangeRecord): string {
return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}`;
}
+ export function forExchangeUpdateFromUrl(exchBaseUrl: string): string {
+ return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}`;
+ }
export function forExchangeCheckRefresh(exch: ExchangeRecord): string {
return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}`;
}
@@ -246,7 +249,7 @@ export async function scheduleRetry(
/**
* Run an operation handler, expect a success result and extract the success value.
*/
-export async function runOperationHandlerForResult<T>(
+export async function unwrapOperationHandlerResultOrThrow<T>(
res: OperationAttemptResult<T>,
): Promise<T> {
switch (res.type) {
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index dbab6607e..d7d8a206f 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -239,7 +239,7 @@ import {
GetReadOnlyAccess,
GetReadWriteAccess,
} from "./util/query.js";
-import { OperationAttemptResult } from "./util/retries.js";
+import { OperationAttemptResult, RetryTags } from "./util/retries.js";
import { TimerAPI, TimerGroup } from "./util/timer.js";
import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
@@ -650,12 +650,16 @@ async function getExchanges(
x.exchangeDetails,
x.exchangeTos,
x.denominations,
+ x.operationRetries,
])
.runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) {
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
- exchanges.push(makeExchangeListItem(r, exchangeDetails));
+ const opRetryRecord = await tx.operationRetries.get(
+ RetryTags.forExchangeUpdate(r),
+ );
+ exchanges.push(makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError));
}
});
return { exchanges };