summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-03-07 17:28:14 +0100
committerFlorian Dold <florian@dold.me>2024-03-07 17:28:42 +0100
commitc22b13eebe0577c2b948a99e42670580d49d60ce (patch)
tree9497f6edba090d03139490d279f233057a23d69e
parent284df1fb5d3e255ef802de389e3fd9ba11a3184f (diff)
downloadwallet-core-c22b13eebe0577c2b948a99e42670580d49d60ce.tar.gz
wallet-core-c22b13eebe0577c2b948a99e42670580d49d60ce.tar.bz2
wallet-core-c22b13eebe0577c2b948a99e42670580d49d60ce.zip
wallet-core: implement and test lost flag for denominations
-rw-r--r--packages/taler-harness/src/integrationtests/test-denom-lost.ts81
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-util/src/wallet-types.ts16
-rw-r--r--packages/taler-wallet-core/src/db.ts7
-rw-r--r--packages/taler-wallet-core/src/denominations.ts5
-rw-r--r--packages/taler-wallet-core/src/exchanges.ts11
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts13
-rw-r--r--packages/taler-wallet-core/src/wallet.ts25
8 files changed, 157 insertions, 3 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-denom-lost.ts b/packages/taler-harness/src/integrationtests/test-denom-lost.ts
new file mode 100644
index 000000000..307ae352a
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-denom-lost.ts
@@ -0,0 +1,81 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import {
+ createSimpleTestkudosEnvironmentV2,
+ withdrawViaBankV2,
+} from "../harness/helpers.js";
+
+/**
+ * Run test for refreshe after a payment.
+ */
+export async function runDenomLostTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bank, exchange, merchant } =
+ await createSimpleTestkudosEnvironmentV2(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV2(t, {
+ walletClient,
+ bank,
+ exchange,
+ amount: "TESTKUDOS:20",
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ const dsBefore = await walletClient.call(
+ WalletApiOperation.TestingGetDenomStats,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ },
+ );
+
+ t.assertDeepEqual(dsBefore.numLost, 0);
+ t.assertDeepEqual(dsBefore.numOffered, dsBefore.numKnown);
+
+ await exchange.stop();
+
+ await exchange.purgeSecmodKeys();
+
+ await exchange.start();
+
+ await walletClient.call(WalletApiOperation.UpdateExchangeEntry, {
+ exchangeBaseUrl: exchange.baseUrl,
+ force: true,
+ });
+
+ const dsAfter = await walletClient.call(
+ WalletApiOperation.TestingGetDenomStats,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ },
+ );
+
+ // All previous denominations were lost
+ t.assertDeepEqual(dsBefore.numOffered, dsAfter.numLost);
+ // But we have new ones!
+ t.assertTrue(dsAfter.numKnown > dsBefore.numKnown);
+}
+
+runDenomLostTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index a294b27f4..566350770 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -36,6 +36,7 @@ import { runBankApiTest } from "./test-bank-api.js";
import { runClaimLoopTest } from "./test-claim-loop.js";
import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
import { runCurrencyScopeTest } from "./test-currency-scope.js";
+import { runDenomLostTest } from "./test-denom-lost.js";
import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
import { runDepositTest } from "./test-deposit.js";
import { runExchangeDepositTest } from "./test-exchange-deposit.js";
@@ -208,6 +209,7 @@ const allTests: TestMainFunction[] = [
runWalletBalanceZeroTest,
runWalletInsufficientBalanceTest,
runWalletWirefeesTest,
+ runDenomLostTest,
];
export interface TestRunSpec {
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 69811969c..0d2713fdd 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -2982,6 +2982,22 @@ export interface TestingWaitTransactionRequest {
txState: TransactionState;
}
+export interface TestingGetDenomStatsRequest {
+ exchangeBaseUrl: string;
+}
+
+export interface TestingGetDenomStatsResponse {
+ numKnown: number;
+ numOffered: number;
+ numLost: number;
+}
+
+export const codecForTestingGetDenomStatsRequest =
+ (): Codec<TestingGetDenomStatsRequest> =>
+ buildCodecForObject<TestingGetDenomStatsRequest>()
+ .property("exchangeBaseUrl", codecForString())
+ .build("TestingGetDenomStatsRequest");
+
export interface WithdrawalExchangeAccountDetails {
/**
* Payto URI to credit the exchange.
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 14621c2d5..b59efe034 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -486,6 +486,13 @@ export interface DenominationRecord {
isRevoked: boolean;
/**
+ * If set to true, the exchange announced that the private key for this
+ * denomination is lost. Thus it can't be used to sign new coins
+ * during withdrawal/refresh/..., but the coins can still be spent.
+ */
+ isLost?: boolean;
+
+ /**
* Base URL of the exchange.
*/
exchangeBaseUrl: string;
diff --git a/packages/taler-wallet-core/src/denominations.ts b/packages/taler-wallet-core/src/denominations.ts
index a539918de..d41307d5d 100644
--- a/packages/taler-wallet-core/src/denominations.ts
+++ b/packages/taler-wallet-core/src/denominations.ts
@@ -24,7 +24,6 @@ import {
AmountString,
DenominationInfo,
Duration,
- durationFromSpec,
FeeDescription,
FeeDescriptionPair,
TalerProtocolTimestamp,
@@ -471,10 +470,10 @@ export function isWithdrawableDenom(
} else {
lastPossibleWithdraw = AbsoluteTime.subtractDuraction(
withdrawExpire,
- durationFromSpec({ minutes: 5 }),
+ Duration.fromSpec({ minutes: 5 }),
);
}
const remaining = Duration.getRemaining(lastPossibleWithdraw, now);
const stillOkay = remaining.d_ms !== 0;
- return started && stillOkay && !d.isRevoked && d.isOffered;
+ return started && stillOkay && !d.isRevoked && d.isOffered && !d.isLost;
}
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
index 335caff62..c44178de8 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -779,6 +779,7 @@ async function downloadExchangeKeysInfo(
exchangeMasterPub: exchangeKeysJsonUnchecked.master_public_key,
isOffered: true,
isRevoked: false,
+ isLost: denomIn.lost ?? false,
value: Amounts.stringify(value),
currency: value.currency,
stampExpireDeposit: timestampProtocolToDb(
@@ -1432,6 +1433,16 @@ export async function updateExchangeFromUrlHandler(
]);
if (oldDenom) {
// FIXME: Do consistency check, report to auditor if necessary.
+ // See https://bugs.taler.net/n/8594
+
+ // Mark lost denominations as lost.
+ if (currentDenom.isLost && !oldDenom.isLost) {
+ logger.warn(
+ `marking denomination ${currentDenom.denomPubHash} of ${exchangeBaseUrl} as lost`,
+ );
+ oldDenom.isLost = true;
+ await tx.denominations.put(currentDenom);
+ }
} else {
await tx.denominations.put(currentDenom);
}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index ace702e88..4f4b24b62 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -117,6 +117,8 @@ import {
StoredBackupList,
TestPayArgs,
TestPayResult,
+ TestingGetDenomStatsRequest,
+ TestingGetDenomStatsResponse,
TestingListTasksForTransactionRequest,
TestingListTasksForTransactionsResponse,
TestingSetTimetravelRequest,
@@ -255,6 +257,7 @@ export enum WalletApiOperation {
RemoveGlobalCurrencyAuditor = "removeGlobalCurrencyAuditor",
ListAssociatedRefreshes = "listAssociatedRefreshes",
TestingListTaskForTransaction = "testingListTasksForTransaction",
+ TestingGetDenomStats = "testingGetDenomStats",
}
// group: Initialization
@@ -1114,6 +1117,15 @@ export type TestingWaitTransactionStateOp = {
};
/**
+ * Get stats about an exchange denomination.
+ */
+export type TestingGetDenomStatsOp = {
+ op: WalletApiOperation.TestingGetDenomStats;
+ request: TestingGetDenomStatsRequest;
+ response: TestingGetDenomStatsResponse;
+};
+
+/**
* Set a coin as (un-)suspended.
* Suspended coins won't be used for payments.
*/
@@ -1238,6 +1250,7 @@ export type WalletOperations = {
[WalletApiOperation.RemoveGlobalCurrencyExchange]: RemoveGlobalCurrencyExchangeOp;
[WalletApiOperation.ListAssociatedRefreshes]: ListAssociatedRefreshesOp;
[WalletApiOperation.TestingListTaskForTransaction]: TestingListTasksForTransactionOp;
+ [WalletApiOperation.TestingGetDenomStats]: TestingGetDenomStatsOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 8c9eee009..46f58ec81 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -57,6 +57,7 @@ import {
TalerErrorCode,
TalerProtocolTimestamp,
TalerUriAction,
+ TestingGetDenomStatsResponse,
TestingListTasksForTransactionsResponse,
TestingWaitTransactionRequest,
TimerAPI,
@@ -126,6 +127,7 @@ import {
codecForStartRefundQueryRequest,
codecForSuspendTransaction,
codecForTestPayArgs,
+ codecForTestingGetDenomStatsRequest,
codecForTestingListTasksForTransactionRequest,
codecForTestingSetTimetravelRequest,
codecForTransactionByIdRequest,
@@ -799,6 +801,29 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
});
return {};
}
+ case WalletApiOperation.TestingGetDenomStats: {
+ const req = codecForTestingGetDenomStatsRequest().decode(payload);
+ const denomStats: TestingGetDenomStatsResponse = {
+ numKnown: 0,
+ numLost: 0,
+ numOffered: 0,
+ };
+ await wex.db.runReadOnlyTx(["denominations"], async (tx) => {
+ const denoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
+ req.exchangeBaseUrl,
+ );
+ for (const d of denoms) {
+ denomStats.numKnown++;
+ if (d.isOffered) {
+ denomStats.numOffered++;
+ }
+ if (d.isLost) {
+ denomStats.numLost++;
+ }
+ }
+ });
+ return denomStats;
+ }
case WalletApiOperation.ListExchanges: {
return await listExchanges(wex);
}