commit bc269e0ff64f00892a8a1947c4e468db3d5aff13
parent 93ea97532b11cfd98eb9bc841c59478b057675ca
Author: Florian Dold <florian@dold.me>
Date: Mon, 25 Aug 2025 01:08:24 +0200
wallet-core: make base url migration work with existing new exchange, other tweaks
Diffstat:
4 files changed, 70 insertions(+), 12 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -100,6 +100,9 @@ import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls.js"
import { runMerchantInstancesTest } from "./test-merchant-instances.js";
import { runMerchantLongpollingTest } from "./test-merchant-longpolling.js";
import { runMerchantRefundApiTest } from "./test-merchant-refund-api.js";
+import { runMerchantSelfProvisionActivationTest } from "./test-merchant-self-provision-activation.js";
+import { runMerchantSelfProvisionForgotPasswordTest } from "./test-merchant-self-provision-forgot-password.js";
+import { runMerchantSelfProvisionInactiveAccountPermissionsTest } from "./test-merchant-self-provision-inactive-account-permissions.js";
import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
import { runMultiExchangeTest } from "./test-multiexchange.js";
import { runOtpTest } from "./test-otp.js";
@@ -161,6 +164,7 @@ import { runWalletDd48Test } from "./test-wallet-dd48.js";
import { runWalletDenomExpireTest } from "./test-wallet-denom-expire.js";
import { runWalletDevExperimentsTest } from "./test-wallet-dev-experiments.js";
import { runWalletDevexpFakeprotoverTest } from "./test-wallet-devexp-fakeprotover.js";
+import { runWalletExchangeMigrationExistingTest } from "./test-wallet-exchange-migration-existing.js";
import { runWalletExchangeMigrationTest } from "./test-wallet-exchange-migration.js";
import { runWalletExchangeUpdateTest } from "./test-wallet-exchange-update.js";
import { runWalletGenDbTest } from "./test-wallet-gendb.js";
@@ -189,9 +193,6 @@ import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js";
-import { runMerchantSelfProvisionActivationTest } from "./test-merchant-self-provision-activation.js";
-import { runMerchantSelfProvisionForgotPasswordTest } from "./test-merchant-self-provision-forgot-password.js";
-import { runMerchantSelfProvisionInactiveAccountPermissionsTest } from "./test-merchant-self-provision-inactive-account-permissions.js";
/**
* Test runner.
@@ -366,6 +367,7 @@ const allTests: TestMainFunction[] = [
runExchangeKycAuthTest,
runTopsPeerTest,
runWalletExchangeMigrationTest,
+ runWalletExchangeMigrationExistingTest,
runKycFormCompressionTest,
runDepositLargeTest,
runDepositTooLargeTest,
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
@@ -163,7 +163,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
* backwards-compatible way or object stores and indices
* are added.
*/
-export const WALLET_DB_MINOR_VERSION = 18;
+export const WALLET_DB_MINOR_VERSION = 19;
declare const symDbProtocolTimestamp: unique symbol;
@@ -2791,6 +2791,11 @@ export interface ExchangeMigrationLogRecord {
reason: ExchangeMigrationReason;
}
+export interface ExchangeBaseUrlFixupRecord {
+ exchangeBaseUrl: string;
+ replacement: string;
+}
+
/**
* Schema definition for the IndexedDB
* wallet database.
@@ -2803,6 +2808,13 @@ export const WalletStoresV1 = {
versionAdded: 18,
indexes: {},
}),
+ exchangeBaseUrlFixups: describeStoreV2({
+ recordCodec: passthroughCodec<ExchangeBaseUrlFixupRecord>(),
+ storeName: "exchangeBaseUrlFixups",
+ keyPath: "exchangeBaseUrl",
+ versionAdded: 19,
+ indexes: {},
+ }),
denomLossEvents: describeStoreV2({
recordCodec: passthroughCodec<DenomLossEventRecord>(),
storeName: "denomLossEvents",
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -45,6 +45,7 @@ import {
EmptyObject,
ExchangeAuditor,
ExchangeDetailedResponse,
+ ExchangeEntryState,
ExchangeGlobalFees,
ExchangeListItem,
ExchangeSignKeyJson,
@@ -1388,6 +1389,17 @@ export async function fetchFreshExchange(
wex.ws.exchangeCache.clear();
}
+ await wex.db.runReadOnlyTx(
+ { storeNames: ["exchangeBaseUrlFixups"] },
+ async (tx) => {
+ const rec = await tx.exchangeBaseUrlFixups.get(baseUrl);
+ if (rec) {
+ logger.warn(`using replacement ${rec.replacement} for ${baseUrl}`);
+ baseUrl = rec.replacement;
+ }
+ },
+ );
+
await wex.taskScheduler.ensureRunning();
await startUpdateExchangeEntry(wex, baseUrl, {
@@ -4134,6 +4146,8 @@ export async function migrateExchange(
return;
}
+ let existingNewExchangeSt: ExchangeEntryState | undefined = undefined;
+
await tx.exchangeBaseUrlMigrationLog.put({
oldExchangeBaseUrl: req.oldExchangeBaseUrl,
newExchangeBaseUrl: req.newExchangeBaseUrl,
@@ -4141,6 +4155,11 @@ export async function migrateExchange(
reason: req.trigger,
});
+ await tx.exchangeBaseUrlFixups.put({
+ exchangeBaseUrl: req.oldExchangeBaseUrl,
+ replacement: req.newExchangeBaseUrl,
+ });
+
{
const denomKeys =
await tx.denominations.indexes.byExchangeBaseUrl.getAllKeys(
@@ -4176,17 +4195,28 @@ export async function migrateExchange(
}
{
- await tx.exchangeDetails.indexes.byExchangeBaseUrl
- .iter(req.oldExchangeBaseUrl)
- .forEachAsync(async (rec) => {
- rec.exchangeBaseUrl = req.newExchangeBaseUrl;
- await tx.exchangeDetails.put(rec);
- });
+ const existingNewExchangeDetails =
+ await tx.exchangeDetails.indexes.byExchangeBaseUrl.get(
+ req.newExchangeBaseUrl,
+ );
+ if (!existingNewExchangeDetails) {
+ await tx.exchangeDetails.indexes.byExchangeBaseUrl
+ .iter(req.oldExchangeBaseUrl)
+ .forEachAsync(async (rec) => {
+ rec.exchangeBaseUrl = req.newExchangeBaseUrl;
+ await tx.exchangeDetails.put(rec);
+ });
+ }
}
{
const rec = await tx.exchanges.get(req.oldExchangeBaseUrl);
if (rec) {
+ existingNewExchangeSt = {
+ exchangeEntryStatus: getExchangeEntryStatusFromRecord(rec),
+ exchangeUpdateStatus: getExchangeUpdateStatusFromRecord(rec),
+ tosStatus: getExchangeTosStatusFromRecord(rec),
+ };
rec.baseUrl = req.newExchangeBaseUrl;
await tx.exchanges.delete(req.oldExchangeBaseUrl);
await tx.exchanges.put(rec);
@@ -4299,7 +4329,7 @@ export async function migrateExchange(
tx.notify({
type: NotificationType.ExchangeStateTransition,
exchangeBaseUrl: req.newExchangeBaseUrl,
- oldExchangeState: undefined,
+ oldExchangeState: existingNewExchangeSt,
newExchangeState: getExchangeState(exch),
});
});
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -1187,6 +1187,8 @@ async function processWithdrawalGroupDialogProposed(
switch (resp.status) {
case HttpStatusCode.NotFound: {
// FIXME: Further inspect the error body
+ const err = await readTalerErrorResponse(resp);
+ logger.warn(`withdrawal operation not found, aborting: ${j2s(err)}`);
await transitionSimple(
ctx,
WithdrawalGroupStatus.DialogProposed,
@@ -3148,6 +3150,8 @@ async function registerReserveWithBank(
switch (httpResp.status) {
case HttpStatusCode.NotFound: {
// FIXME: Inspect particular status code
+ const err = await readTalerErrorResponse(httpResp);
+ logger.warn(`withdrawal operation not found, aborting: ${j2s(err)}`);
await transitionSimple(
ctx,
WithdrawalGroupStatus.PendingRegisteringBank,
@@ -3800,7 +3804,7 @@ export async function confirmWithdrawal(
req: ConfirmWithdrawalRequest,
): Promise<AcceptWithdrawalResponse> {
const parsedTx = parseTransactionIdentifier(req.transactionId);
- const selectedExchange = req.exchangeBaseUrl;
+ let selectedExchange: string = req.exchangeBaseUrl;
const instructedAmount =
req.amount == null ? undefined : Amounts.parseOrThrow(req.amount);
@@ -3823,6 +3827,16 @@ export async function confirmWithdrawal(
throw Error("not a bank integrated withdrawal");
}
+ await wex.db.runReadOnlyTx(
+ { storeNames: ["exchangeBaseUrlFixups"] },
+ async (tx) => {
+ const rec = await tx.exchangeBaseUrlFixups.get(selectedExchange);
+ if (rec) {
+ selectedExchange = rec.replacement;
+ }
+ },
+ );
+
let instructedCurrency: string;
if (instructedAmount) {
instructedCurrency = instructedAmount.currency;