aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-util/src/backup-types.ts11
-rw-r--r--packages/taler-util/src/taler-types.ts54
-rw-r--r--packages/taler-util/src/wallet-types.ts81
-rw-r--r--packages/taler-wallet-cli/src/index.ts4
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts2
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts9
-rw-r--r--packages/taler-wallet-core/src/db.ts43
-rw-r--r--packages/taler-wallet-core/src/internal-wallet-state.ts4
-rw-r--r--packages/taler-wallet-core/src/operations/backup/export.ts10
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/balance.ts25
-rw-r--r--packages/taler-wallet-core/src/operations/common.ts26
-rw-r--r--packages/taler-wallet-core/src/operations/pay-merchant.ts46
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer.ts8
-rw-r--r--packages/taler-wallet-core/src/operations/recoup.ts42
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts24
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts8
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts7
-rw-r--r--packages/taler-wallet-core/src/wallet.ts32
20 files changed, 237 insertions, 210 deletions
diff --git a/packages/taler-util/src/backup-types.ts b/packages/taler-util/src/backup-types.ts
index 6c7b203b5..5d53f178e 100644
--- a/packages/taler-util/src/backup-types.ts
+++ b/packages/taler-util/src/backup-types.ts
@@ -475,6 +475,7 @@ export interface BackupRecoupGroup {
timestamp_finish?: TalerProtocolTimestamp;
finish_clock?: TalerProtocolTimestamp;
+ // FIXME: Use some enum here!
finish_is_failure?: boolean;
/**
@@ -483,7 +484,6 @@ export interface BackupRecoupGroup {
coins: {
coin_pub: string;
recoup_finished: boolean;
- old_amount: BackupAmountString;
}[];
}
@@ -582,9 +582,14 @@ export interface BackupCoin {
denom_sig: UnblindedSignature;
/**
- * Amount that's left on the coin.
+ * Information about where and how the coin was spent.
*/
- current_amount: BackupAmountString;
+ spend_allocation:
+ | {
+ id: string;
+ amount: BackupAmountString;
+ }
+ | undefined;
/**
* Blinding key used when withdrawing the coin.
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts
index de88fef69..71ceb7939 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -968,60 +968,6 @@ export class WithdrawBatchResponse {
ev_sigs: WithdrawResponse[];
}
-/**
- * Easy to process format for the public data of coins
- * managed by the wallet.
- */
-export interface CoinDumpJson {
- coins: Array<{
- /**
- * The coin's denomination's public key.
- */
- denom_pub: DenominationPubKey;
- /**
- * Hash of denom_pub.
- */
- denom_pub_hash: string;
- /**
- * Value of the denomination (without any fees).
- */
- denom_value: string;
- /**
- * Public key of the coin.
- */
- coin_pub: string;
- /**
- * Base URL of the exchange for the coin.
- */
- exchange_base_url: string;
- /**
- * Remaining value on the coin, to the knowledge of
- * the wallet.
- */
- remaining_value: string;
- /**
- * Public key of the parent coin.
- * Only present if this coin was obtained via refreshing.
- */
- refresh_parent_coin_pub: string | undefined;
- /**
- * Public key of the reserve for this coin.
- * Only present if this coin was obtained via refreshing.
- */
- withdrawal_reserve_pub: string | undefined;
- /**
- * Is the coin suspended?
- * Suspended coins are not considered for payments.
- */
- coin_suspended: boolean;
-
- /**
- * Information about the age restriction
- */
- ageCommitmentProof: AgeCommitmentProof | undefined;
- }>;
-}
-
export interface MerchantPayResponse {
sig: string;
}
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index d4de53972..54f4c54a2 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -63,7 +63,10 @@ import {
ExchangeAuditor,
UnblindedSignature,
} from "./taler-types.js";
-import { OrderShortInfo, codecForOrderShortInfo } from "./transactions-types.js";
+import {
+ OrderShortInfo,
+ codecForOrderShortInfo,
+} from "./transactions-types.js";
import { BackupRecovery } from "./backup-types.js";
import { PaytoUri } from "./payto.js";
import { TalerErrorCode } from "./taler-error-codes.js";
@@ -141,6 +144,77 @@ export function mkAmount(
return { value, fraction, currency };
}
+/**
+ * Status of a coin.
+ */
+export enum CoinStatus {
+ /**
+ * Withdrawn and never shown to anybody.
+ */
+ Fresh = "fresh",
+
+ /**
+ * Fresh, but currently marked as "suspended", thus won't be used
+ * for spending. Used for testing.
+ */
+ FreshSuspended = "fresh-suspended",
+
+ /**
+ * A coin that has been spent and refreshed.
+ */
+ Dormant = "dormant",
+}
+
+/**
+ * Easy to process format for the public data of coins
+ * managed by the wallet.
+ */
+export interface CoinDumpJson {
+ coins: Array<{
+ /**
+ * The coin's denomination's public key.
+ */
+ denom_pub: DenominationPubKey;
+ /**
+ * Hash of denom_pub.
+ */
+ denom_pub_hash: string;
+ /**
+ * Value of the denomination (without any fees).
+ */
+ denom_value: string;
+ /**
+ * Public key of the coin.
+ */
+ coin_pub: string;
+ /**
+ * Base URL of the exchange for the coin.
+ */
+ exchange_base_url: string;
+ /**
+ * Public key of the parent coin.
+ * Only present if this coin was obtained via refreshing.
+ */
+ refresh_parent_coin_pub: string | undefined;
+ /**
+ * Public key of the reserve for this coin.
+ * Only present if this coin was obtained via refreshing.
+ */
+ withdrawal_reserve_pub: string | undefined;
+ coin_status: CoinStatus;
+ spend_allocation:
+ | {
+ id: string;
+ amount: string;
+ }
+ | undefined;
+ /**
+ * Information about the age restriction
+ */
+ ageCommitmentProof: AgeCommitmentProof | undefined;
+ }>;
+}
+
export enum ConfirmPayResultType {
Done = "done",
Pending = "pending",
@@ -568,10 +642,11 @@ export enum RefreshReason {
}
/**
- * Wrapper for coin public keys.
+ * Request to refresh a single coin.
*/
-export interface CoinPublicKey {
+export interface CoinRefreshRequest {
readonly coinPub: string;
+ readonly amount: AmountJson;
}
/**
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
index 941a2f28f..b3abbac6f 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -1105,9 +1105,7 @@ advancedCli
console.log(`coin ${coin.coin_pub}`);
console.log(` exchange ${coin.exchange_base_url}`);
console.log(` denomPubHash ${coin.denom_pub_hash}`);
- console.log(
- ` remaining amount ${Amounts.stringify(coin.remaining_value)}`,
- );
+ console.log(` status ${coin.coin_status}`);
}
});
});
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
index e4e96a180..8d1f6e873 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
@@ -193,7 +193,7 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {
.map((x) => x.amountEffective),
).amount;
- t.assertAmountEquals("TESTKUDOS:8.33", effective);
+ t.assertAmountEquals("TESTKUDOS:8.59", effective);
}
await t.shutdown();
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts
index c16a85f19..03c446db3 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts
@@ -22,7 +22,7 @@
/**
* Imports.
*/
-import { Amounts } from "@gnu-taler/taler-util";
+import { Amounts, CoinStatus } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
import {
@@ -32,7 +32,7 @@ import {
MerchantService,
setupDb,
WalletCli,
- getPayto
+ getPayto,
} from "../harness/harness.js";
import { SimpleTestEnvironment } from "../harness/helpers.js";
@@ -184,7 +184,10 @@ export async function runWallettestingTest(t: GlobalTestState) {
let susp: string | undefined;
{
for (const c of coinDump.coins) {
- if (0 === Amounts.cmp(c.remaining_value, "TESTKUDOS:8")) {
+ if (
+ c.coin_status === CoinStatus.Fresh &&
+ 0 === Amounts.cmp(c.denom_value, "TESTKUDOS:8")
+ ) {
susp = c.coin_pub;
}
}
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index c301ee457..b785efed8 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -49,6 +49,8 @@ import {
ExchangeGlobalFees,
DenomSelectionState,
TransactionIdStr,
+ CoinRefreshRequest,
+ CoinStatus,
} from "@gnu-taler/taler-util";
import { RetryInfo, RetryTags } from "./util/retries.js";
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
@@ -603,27 +605,6 @@ export interface PlanchetRecord {
ageCommitmentProof?: AgeCommitmentProof;
}
-/**
- * Status of a coin.
- */
-export enum CoinStatus {
- /**
- * Withdrawn and never shown to anybody.
- */
- Fresh = "fresh",
-
- /**
- * Fresh, but currently marked as "suspended", thus won't be used
- * for spending. Used for testing.
- */
- FreshSuspended = "fresh-suspended",
-
- /**
- * A coin that has been spent and refreshed.
- */
- Dormant = "dormant",
-}
-
export enum CoinSourceType {
Withdraw = "withdraw",
Refresh = "refresh",
@@ -693,14 +674,6 @@ export interface CoinRecord {
denomSig: UnblindedSignature;
/**
- * Amount that's left on the coin.
- *
- * FIXME: This is pretty redundant with "allocation" and "status".
- * Do we really need this?
- */
- currentAmount: AmountJson;
-
- /**
* Base URL that identifies the exchange from which we got the
* coin.
*/
@@ -732,7 +705,7 @@ export interface CoinRecord {
* - Diagnostics
* - Idempotency of applying a coin selection (e.g. after re-selection)
*/
- allocation: CoinAllocation | undefined;
+ spendAllocation: CoinAllocation | undefined;
/**
* Maximum age of purchases that can be made with this coin.
@@ -1462,17 +1435,10 @@ export interface RecoupGroupRecord {
recoupFinishedPerCoin: boolean[];
/**
- * We store old amount (i.e. before recoup) of recouped coins here,
- * as the balance of a recouped coin is set to zero when the
- * recoup group is created.
- */
- oldAmountPerCoin: AmountJson[];
-
- /**
* Public keys of coins that should be scheduled for refreshing
* after all individual recoups are done.
*/
- scheduleRefreshCoins: string[];
+ scheduleRefreshCoins: CoinRefreshRequest[];
}
export enum BackupProviderStateTag {
@@ -1875,7 +1841,6 @@ export const WalletStoresV1 = {
"exchangeTos",
describeContents<ExchangeTosRecord>({
keyPath: ["exchangeBaseUrl", "etag"],
- autoIncrement: true,
}),
{},
),
diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts
index bc956bd17..ebb9cdb9b 100644
--- a/packages/taler-wallet-core/src/internal-wallet-state.ts
+++ b/packages/taler-wallet-core/src/internal-wallet-state.ts
@@ -38,7 +38,7 @@ import {
CancellationToken,
DenominationInfo,
RefreshGroupId,
- CoinPublicKey,
+ CoinRefreshRequest,
RefreshReason,
} from "@gnu-taler/taler-util";
import { CryptoDispatcher } from "./crypto/workers/cryptoDispatcher.js";
@@ -86,7 +86,7 @@ export interface RefreshOperations {
refreshGroups: typeof WalletStoresV1.refreshGroups;
coinAvailability: typeof WalletStoresV1.coinAvailability;
}>,
- oldCoinPubs: CoinPublicKey[],
+ oldCoinPubs: CoinRefreshRequest[],
reason: RefreshReason,
): Promise<RefreshGroupId>;
}
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts
index 30e61e382..1472b1b90 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -54,6 +54,7 @@ import {
BACKUP_VERSION_MINOR,
canonicalizeBaseUrl,
canonicalJson,
+ CoinStatus,
encodeCrock,
getRandomBytes,
hash,
@@ -63,7 +64,6 @@ import {
} from "@gnu-taler/taler-util";
import {
CoinSourceType,
- CoinStatus,
ConfigRecordKey,
DenominationRecord,
PurchaseStatus,
@@ -206,7 +206,6 @@ export async function exportBackup(
coins: recoupGroup.coinPubs.map((x, i) => ({
coin_pub: x,
recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
- old_amount: Amounts.stringify(recoupGroup.oldAmountPerCoin[i]),
})),
});
});
@@ -259,8 +258,13 @@ export async function exportBackup(
blinding_key: coin.blindingKey,
coin_priv: coin.coinPriv,
coin_source: bcs,
- current_amount: Amounts.stringify(coin.currentAmount),
fresh: coin.status === CoinStatus.Fresh,
+ spend_allocation: coin.spendAllocation
+ ? {
+ amount: coin.spendAllocation.amount,
+ id: coin.spendAllocation.id,
+ }
+ : undefined,
denom_sig: coin.denomSig,
});
});
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index 3bbb7d798..9c5eea9af 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -27,6 +27,7 @@ import {
BackupRefundState,
BackupWgType,
codecForContractTerms,
+ CoinStatus,
DenomKeyType,
DenomSelectionState,
j2s,
@@ -41,10 +42,8 @@ import {
CoinRecord,
CoinSource,
CoinSourceType,
- CoinStatus,
DenominationRecord,
DenominationVerificationStatus,
- OperationStatus,
ProposalDownloadInfo,
PurchaseStatus,
PurchasePayInfo,
@@ -272,7 +271,6 @@ export async function importCoin(
blindingKey: backupCoin.blinding_key,
coinEvHash: compCoin.coinEvHash,
coinPriv: backupCoin.coin_priv,
- currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
denomSig: backupCoin.denom_sig,
coinPub: compCoin.coinPub,
exchangeBaseUrl,
@@ -284,7 +282,7 @@ export async function importCoin(
// FIXME!
ageCommitmentProof: undefined,
// FIXME!
- allocation: undefined,
+ spendAllocation: undefined,
};
if (coinRecord.status === CoinStatus.Fresh) {
await makeCoinAvailable(ws, tx, coinRecord);
diff --git a/packages/taler-wallet-core/src/operations/balance.ts b/packages/taler-wallet-core/src/operations/balance.ts
index 44357fdf4..3db66b5d9 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -23,7 +23,7 @@ import {
Amounts,
Logger,
} from "@gnu-taler/taler-util";
-import { CoinStatus, WalletStoresV1 } from "../db.js";
+import { WalletStoresV1 } from "../db.js";
import { GetReadOnlyAccess } from "../util/query.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -42,6 +42,7 @@ export async function getBalancesInsideTransaction(
ws: InternalWalletState,
tx: GetReadOnlyAccess<{
coins: typeof WalletStoresV1.coins;
+ coinAvailability: typeof WalletStoresV1.coinAvailability;
refreshGroups: typeof WalletStoresV1.refreshGroups;
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
}>,
@@ -64,12 +65,14 @@ export async function getBalancesInsideTransaction(
return balanceStore[currency];
};
- await tx.coins.iter().forEach((c) => {
- // Only count fresh coins, as dormant coins will
- // already be in a refresh session.
- if (c.status === CoinStatus.Fresh) {
- const b = initBalance(c.currentAmount.currency);
- b.available = Amounts.add(b.available, c.currentAmount).amount;
+ await tx.coinAvailability.iter().forEach((ca) => {
+ const b = initBalance(ca.currency);
+ for (let i = 0; i < ca.freshCoinCount; i++) {
+ b.available = Amounts.add(b.available, {
+ currency: ca.currency,
+ fraction: ca.amountFrac,
+ value: ca.amountVal,
+ }).amount;
}
});
@@ -139,7 +142,13 @@ export async function getBalances(
logger.trace("starting to compute balance");
const wbal = await ws.db
- .mktx((x) => [x.coins, x.refreshGroups, x.purchases, x.withdrawalGroups])
+ .mktx((x) => [
+ x.coins,
+ x.coinAvailability,
+ x.refreshGroups,
+ x.purchases,
+ x.withdrawalGroups,
+ ])
.runReadOnly(async (tx) => {
return getBalancesInsideTransaction(ws, tx);
});
diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts
index d17530c7f..5e02f3d7b 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -20,6 +20,8 @@
import {
AmountJson,
Amounts,
+ CoinRefreshRequest,
+ CoinStatus,
j2s,
Logger,
RefreshReason,
@@ -29,7 +31,7 @@ import {
TransactionIdStr,
TransactionType,
} from "@gnu-taler/taler-util";
-import { WalletStoresV1, CoinStatus, CoinRecord } from "../db.js";
+import { WalletStoresV1, CoinRecord } from "../db.js";
import { makeErrorDetail, TalerError } from "../errors.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
@@ -103,11 +105,19 @@ export async function spendCoins(
}>,
csi: CoinsSpendInfo,
): Promise<void> {
+ let refreshCoinPubs: CoinRefreshRequest[] = [];
for (let i = 0; i < csi.coinPubs.length; i++) {
const coin = await tx.coins.get(csi.coinPubs[i]);
if (!coin) {
throw Error("coin allocated for payment doesn't exist anymore");
}
+ const denom = await ws.getDenomInfo(
+ ws,
+ tx,
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ );
+ checkDbInvariant(!!denom);
const coinAvailability = await tx.coinAvailability.get([
coin.exchangeBaseUrl,
coin.denomPubHash,
@@ -116,7 +126,7 @@ export async function spendCoins(
checkDbInvariant(!!coinAvailability);
const contrib = csi.contributions[i];
if (coin.status !== CoinStatus.Fresh) {
- const alloc = coin.allocation;
+ const alloc = coin.spendAllocation;
if (!alloc) {
continue;
}
@@ -131,15 +141,18 @@ export async function spendCoins(
continue;
}
coin.status = CoinStatus.Dormant;
- coin.allocation = {
+ coin.spendAllocation = {
id: csi.allocationId,
amount: Amounts.stringify(contrib),
};
- const remaining = Amounts.sub(coin.currentAmount, contrib);
+ const remaining = Amounts.sub(denom.value, contrib);
if (remaining.saturated) {
throw Error("not enough remaining balance on coin for payment");
}
- coin.currentAmount = remaining.amount;
+ refreshCoinPubs.push({
+ amount: remaining.amount,
+ coinPub: coin.coinPub,
+ });
checkDbInvariant(!!coinAvailability);
if (coinAvailability.freshCoinCount === 0) {
throw Error(
@@ -150,9 +163,6 @@ export async function spendCoins(
await tx.coins.put(coin);
await tx.coinAvailability.put(coinAvailability);
}
- const refreshCoinPubs = csi.coinPubs.map((x) => ({
- coinPub: x,
- }));
await ws.refreshOps.createRefreshGroup(
ws,
tx,
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 6b14b60c6..2b0ea1f96 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -40,7 +40,8 @@ import {
codecForMerchantPayResponse,
codecForProposal,
CoinDepositPermission,
- CoinPublicKey,
+ CoinRefreshRequest,
+ CoinStatus,
ConfirmPayResult,
ConfirmPayResultType,
ContractTerms,
@@ -78,7 +79,6 @@ import {
AllowedExchangeInfo,
BackupProviderStateTag,
CoinRecord,
- CoinStatus,
DenominationRecord,
PurchaseRecord,
PurchaseStatus,
@@ -2084,7 +2084,7 @@ async function applySuccessfulRefund(
denominations: typeof WalletStoresV1.denominations;
}>,
p: PurchaseRecord,
- refreshCoinsMap: Record<string, { coinPub: string }>,
+ refreshCoinsMap: Record<string, CoinRefreshRequest>,
r: MerchantCoinRefundSuccessStatus,
): Promise<void> {
// FIXME: check signature before storing it as valid!
@@ -2102,31 +2102,23 @@ async function applySuccessfulRefund(
if (!denom) {
throw Error("inconsistent database");
}
- refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
const refundAmount = Amounts.parseOrThrow(r.refund_amount);
const refundFee = denom.fees.feeRefund;
+ const amountLeft = Amounts.sub(refundAmount, refundFee).amount;
coin.status = CoinStatus.Dormant;
- coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount;
- coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount;
- logger.trace(`coin amount after is ${Amounts.stringify(coin.currentAmount)}`);
await tx.coins.put(coin);
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
.iter(coin.exchangeBaseUrl)
.toArray();
-
- const amountLeft = Amounts.sub(
- Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
- .amount,
- denom.fees.feeRefund,
- ).amount;
-
const totalRefreshCostBound = getTotalRefreshCost(
allDenoms,
DenominationRecord.toDenomInfo(denom),
amountLeft,
);
+ refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub, amount: amountLeft };
+
p.refunds[refundKey] = {
type: RefundState.Applied,
obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
@@ -2167,9 +2159,9 @@ async function storePendingRefund(
.iter(coin.exchangeBaseUrl)
.toArray();
+ // Refunded amount after fees.
const amountLeft = Amounts.sub(
- Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
- .amount,
+ Amounts.parseOrThrow(r.refund_amount),
denom.fees.feeRefund,
).amount;
@@ -2197,7 +2189,7 @@ async function storeFailedRefund(
denominations: typeof WalletStoresV1.denominations;
}>,
p: PurchaseRecord,
- refreshCoinsMap: Record<string, { coinPub: string }>,
+ refreshCoinsMap: Record<string, CoinRefreshRequest>,
r: MerchantCoinRefundFailureStatus,
): Promise<void> {
const refundKey = getRefundKey(r);
@@ -2221,8 +2213,7 @@ async function storeFailedRefund(
.toArray();
const amountLeft = Amounts.sub(
- Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
- .amount,
+ Amounts.parseOrThrow(r.refund_amount),
denom.fees.feeRefund,
).amount;
@@ -2246,6 +2237,7 @@ async function storeFailedRefund(
if (p.purchaseStatus === PurchaseStatus.AbortingWithRefund) {
// Refund failed because the merchant didn't even try to deposit
// the coin yet, so we try to refresh.
+ // FIXME: Is this case tested?!
if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) {
const coin = await tx.coins.get(r.coin_pub);
if (!coin) {
@@ -2271,14 +2263,11 @@ async function storeFailedRefund(
contrib = payCoinSelection.coinContributions[i];
}
}
- if (contrib) {
- coin.currentAmount = Amounts.add(coin.currentAmount, contrib).amount;
- coin.currentAmount = Amounts.sub(
- coin.currentAmount,
- denom.fees.feeRefund,
- ).amount;
- }
- refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
+ // FIXME: Is this case tested?!
+ refreshCoinsMap[coin.coinPub] = {
+ coinPub: coin.coinPub,
+ amount: amountLeft,
+ };
await tx.coins.put(coin);
}
}
@@ -2308,7 +2297,7 @@ async function acceptRefunds(
return;
}
- const refreshCoinsMap: Record<string, CoinPublicKey> = {};
+ const refreshCoinsMap: Record<string, CoinRefreshRequest> = {};
for (const refundStatus of refunds) {
const refundKey = getRefundKey(refundStatus);
@@ -2350,6 +2339,7 @@ async function acceptRefunds(
}
const refreshCoinsPubs = Object.values(refreshCoinsMap);
+ logger.info(`refreshCoinMap ${j2s(refreshCoinsMap)}`);
if (refreshCoinsPubs.length > 0) {
await createRefreshGroup(
ws,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts
index ffc49c24c..3b65fba6b 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -36,6 +36,7 @@ import {
codecForAmountString,
codecForAny,
codecForExchangeGetContractResponse,
+ CoinStatus,
constructPayPullUri,
constructPayPushUri,
ContractTermsUtil,
@@ -63,17 +64,16 @@ import {
WalletAccountMergeFlags,
} from "@gnu-taler/taler-util";
import {
- CoinStatus,
- WithdrawalGroupStatus,
+ ReserveRecord,
WalletStoresV1,
+ WithdrawalGroupStatus,
WithdrawalRecordType,
- ReserveRecord,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
+import { makeTransactionId, spendCoins } from "../operations/common.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { checkDbInvariant } from "../util/invariants.js";
import { GetReadOnlyAccess } from "../util/query.js";
-import { spendCoins, makeTransactionId } from "../operations/common.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { internalCreateWithdrawalGroup } from "./withdraw.js";
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts
index ff6bb4efc..d3bcde048 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -28,6 +28,7 @@ import {
Amounts,
codecForRecoupConfirmation,
codecForReserveStatus,
+ CoinStatus,
encodeCrock,
getRandomBytes,
j2s,
@@ -40,7 +41,6 @@ import {
import {
CoinRecord,
CoinSourceType,
- CoinStatus,
RecoupGroupRecord,
RefreshCoinSource,
WalletStoresV1,
@@ -50,6 +50,7 @@ import {
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import { checkDbInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js";
import {
OperationAttemptResult,
@@ -180,8 +181,6 @@ async function recoupWithdrawCoin(
return;
}
updatedCoin.status = CoinStatus.Dormant;
- const currency = updatedCoin.currentAmount.currency;
- updatedCoin.currentAmount = Amounts.getZero(currency);
await tx.coins.put(updatedCoin);
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
});
@@ -265,16 +264,25 @@ async function recoupRefreshCoin(
logger.warn("refresh old coin for recoup not found");
return;
}
- revokedCoin.status = CoinStatus.Dormant;
- oldCoin.currentAmount = Amounts.add(
- oldCoin.currentAmount,
- recoupGroup.oldAmountPerCoin[coinIdx],
- ).amount;
- logger.trace(
- "recoup: setting old coin amount to",
- Amounts.stringify(oldCoin.currentAmount),
+ const oldCoinDenom = await ws.getDenomInfo(
+ ws,
+ tx,
+ oldCoin.exchangeBaseUrl,
+ oldCoin.denomPubHash,
);
- recoupGroup.scheduleRefreshCoins.push(oldCoin.coinPub);
+ const revokedCoinDenom = await ws.getDenomInfo(
+ ws,
+ tx,
+ revokedCoin.exchangeBaseUrl,
+ revokedCoin.denomPubHash,
+ );
+ checkDbInvariant(!!oldCoinDenom);
+ checkDbInvariant(!!revokedCoinDenom);
+ revokedCoin.status = CoinStatus.Dormant;
+ recoupGroup.scheduleRefreshCoins.push({
+ coinPub: oldCoin.coinPub,
+ amount: Amounts.sub(oldCoinDenom.value, revokedCoinDenom.value).amount,
+ });
await tx.coins.put(revokedCoin);
await tx.coins.put(oldCoin);
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
@@ -410,7 +418,7 @@ export async function processRecoupGroupHandler(
const refreshGroupId = await createRefreshGroup(
ws,
tx,
- rg2.scheduleRefreshCoins.map((x) => ({ coinPub: x })),
+ rg2.scheduleRefreshCoins,
RefreshReason.Recoup,
);
processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => {
@@ -442,8 +450,6 @@ export async function createRecoupGroup(
timestampFinished: undefined,
timestampStarted: TalerProtocolTimestamp.now(),
recoupFinishedPerCoin: coinPubs.map(() => false),
- // Will be populated later
- oldAmountPerCoin: [],
scheduleRefreshCoins: [],
};
@@ -454,12 +460,6 @@ export async function createRecoupGroup(
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
continue;
}
- if (Amounts.isZero(coin.currentAmount)) {
- await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
- continue;
- }
- recoupGroup.oldAmountPerCoin[coinIdx] = coin.currentAmount;
- coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
await tx.coins.put(coin);
}
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 83ab32f20..c7d2c320e 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -23,8 +23,9 @@ import {
amountToPretty,
codecForExchangeMeltResponse,
codecForExchangeRevealResponse,
- CoinPublicKey,
CoinPublicKeyString,
+ CoinRefreshRequest,
+ CoinStatus,
DenominationInfo,
DenomKeyType,
Duration,
@@ -55,9 +56,7 @@ import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js";
import {
CoinRecord,
CoinSourceType,
- CoinStatus,
DenominationRecord,
- OperationStatus,
RefreshCoinStatus,
RefreshGroupRecord,
RefreshOperationStatus,
@@ -672,7 +671,6 @@ async function refreshReveal(
blindingKey: pc.blindingKey,
coinPriv: pc.coinPriv,
coinPub: pc.coinPub,
- currentAmount: ncd.value,
denomPubHash: ncd.denomPubHash,
denomSig,
exchangeBaseUrl: oldCoin.exchangeBaseUrl,
@@ -684,7 +682,7 @@ async function refreshReveal(
coinEvHash: pc.coinEvHash,
maxAge: pc.maxAge,
ageCommitmentProof: pc.ageCommitmentProof,
- allocation: undefined,
+ spendAllocation: undefined,
};
coins.push(coin);
@@ -845,7 +843,7 @@ export async function createRefreshGroup(
refreshGroups: typeof WalletStoresV1.refreshGroups;
coinAvailability: typeof WalletStoresV1.coinAvailability;
}>,
- oldCoinPubs: CoinPublicKey[],
+ oldCoinPubs: CoinRefreshRequest[],
reason: RefreshReason,
): Promise<RefreshGroupId> {
const refreshGroupId = encodeCrock(getRandomBytes(32));
@@ -908,9 +906,8 @@ export async function createRefreshGroup(
default:
assertUnreachable(coin.status);
}
- const refreshAmount = coin.currentAmount;
+ const refreshAmount = ocp.amount;
inputPerCoin.push(refreshAmount);
- coin.currentAmount = Amounts.getZero(refreshAmount.currency);
await tx.coins.put(coin);
const denoms = await getDenoms(coin.exchangeBaseUrl);
const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
@@ -1008,7 +1005,7 @@ export async function autoRefresh(
const coins = await tx.coins.indexes.byBaseUrl
.iter(exchangeBaseUrl)
.toArray();
- const refreshCoins: CoinPublicKey[] = [];
+ const refreshCoins: CoinRefreshRequest[] = [];
for (const coin of coins) {
if (coin.status !== CoinStatus.Fresh) {
continue;
@@ -1023,7 +1020,14 @@ export async function autoRefresh(
}
const executeThreshold = getAutoRefreshExecuteThreshold(denom);
if (AbsoluteTime.isExpired(executeThreshold)) {
- refreshCoins.push(coin);
+ refreshCoins.push({
+ coinPub: coin.coinPub,
+ amount: {
+ value: denom.amountVal,
+ fraction: denom.amountFrac,
+ currency: denom.currency,
+ },
+ });
} else {
const checkThreshold = getAutoRefreshCheckThreshold(denom);
minCheckThreshold = AbsoluteTime.min(
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts
index b74e1182a..f98d69e26 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -24,6 +24,7 @@ import {
BlindedDenominationSignature,
codecForMerchantTipResponseV2,
codecForTipPickupGetResponse,
+ CoinStatus,
DenomKeyType,
encodeCrock,
getRandomBytes,
@@ -41,7 +42,6 @@ import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
import {
CoinRecord,
CoinSourceType,
- CoinStatus,
DenominationRecord,
TipRecord,
} from "../db.js";
@@ -311,7 +311,6 @@ export async function processTip(
coinIndex: i,
walletTipId: walletTipId,
},
- currentAmount: DenominationRecord.getValue(denom),
denomPubHash: denom.denomPubHash,
denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
@@ -319,7 +318,7 @@ export async function processTip(
coinEvHash: planchet.coinEvHash,
maxAge: AgeRestriction.AGE_UNRESTRICTED,
ageCommitmentProof: planchet.ageCommitmentProof,
- allocation: undefined,
+ spendAllocation: undefined,
});
}
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index c7ff4161a..1e7f982bc 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -540,7 +540,6 @@ function buildTransactionForTip(
/**
* For a set of refund with the same executionTime.
- *
*/
interface MergedRefundInfo {
executionTime: TalerProtocolTimestamp;
@@ -556,7 +555,7 @@ function mergeRefundByExecutionTime(
const refundByExecTime = rs.reduce((prev, refund) => {
const key = `${refund.executionTime.t_s}`;
- //refunds counts if applied
+ // refunds count if applied
const effective =
refund.type === RefundState.Applied
? Amounts.sub(
@@ -582,7 +581,10 @@ function mergeRefundByExecutionTime(
v.amountAppliedEffective,
effective,
).amount;
- v.amountAppliedRaw = Amounts.add(v.amountAppliedRaw).amount;
+ v.amountAppliedRaw = Amounts.add(
+ v.amountAppliedRaw,
+ refund.refundAmount,
+ ).amount;
v.firstTimestamp = TalerProtocolTimestamp.min(
v.firstTimestamp,
refund.obtainedTime,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index a258c5d76..d7627e6cf 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -36,6 +36,7 @@ import {
codecForWithdrawBatchResponse,
codecForWithdrawOperationStatusResponse,
codecForWithdrawResponse,
+ CoinStatus,
DenomKeyType,
DenomSelectionState,
Duration,
@@ -57,7 +58,6 @@ import {
TransactionType,
UnblindedSignature,
URL,
- VersionMatchResult,
WithdrawBatchResponse,
WithdrawResponse,
WithdrawUriInfoResponse,
@@ -66,10 +66,8 @@ import { EddsaKeypair } from "../crypto/cryptoImplementation.js";
import {
CoinRecord,
CoinSourceType,
- CoinStatus,
DenominationRecord,
DenominationVerificationStatus,
- ExchangeTosRecord,
PlanchetRecord,
PlanchetStatus,
WalletStoresV1,
@@ -736,7 +734,6 @@ async function processPlanchetVerifyAndStoreCoin(
blindingKey: planchet.blindingKey,
coinPriv: planchet.coinPriv,
coinPub: planchet.coinPub,
- currentAmount: denomInfo.value,
denomPubHash: planchet.denomPubHash,
denomSig,
coinEvHash: planchet.coinEvHash,
@@ -750,7 +747,7 @@ async function processPlanchetVerifyAndStoreCoin(
},
maxAge: planchet.maxAge,
ageCommitmentProof: planchet.ageCommitmentProof,
- allocation: undefined,
+ spendAllocation: undefined,
};
const planchetCoinPub = planchet.coinPub;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index ef7a745ab..ef41c5101 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -95,6 +95,8 @@ import {
WalletNotification,
codecForSetDevModeRequest,
ExchangeTosStatusDetails,
+ CoinRefreshRequest,
+ CoinStatus,
} from "@gnu-taler/taler-util";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import {
@@ -105,11 +107,9 @@ import { clearDatabase } from "./db-utils.js";
import {
AuditorTrustRecord,
CoinSourceType,
- CoinStatus,
ConfigRecordKey,
DenominationRecord,
ExchangeDetailsRecord,
- ExchangeTosRecord,
exportDb,
importDb,
WalletStoresV1,
@@ -934,10 +934,15 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
}),
exchange_base_url: c.exchangeBaseUrl,
refresh_parent_coin_pub: refreshParentCoinPub,
- remaining_value: Amounts.stringify(c.currentAmount),
withdrawal_reserve_pub: withdrawalReservePub,
- coin_suspended: c.status === CoinStatus.FreshSuspended,
+ coin_status: c.status,
ageCommitmentProof: c.ageCommitmentProof,
+ spend_allocation: c.spendAllocation
+ ? {
+ amount: c.spendAllocation.amount,
+ id: c.spendAllocation.id,
+ }
+ : undefined,
});
}
});
@@ -1153,7 +1158,6 @@ async function dispatchRequestInternal(
}
case "forceRefresh": {
const req = codecForForceRefreshRequest().decode(payload);
- const coinPubs = req.coinPubList.map((x) => ({ coinPub: x }));
const refreshGroupId = await ws.db
.mktx((x) => [
x.refreshGroups,
@@ -1162,6 +1166,24 @@ async function dispatchRequestInternal(
x.coins,
])
.runReadWrite(async (tx) => {
+ let coinPubs: CoinRefreshRequest[] = [];
+ for (const c of req.coinPubList) {
+ const coin = await tx.coins.get(c);
+ if (!coin) {
+ throw Error(`coin (pubkey ${c}) not found`);
+ }
+ const denom = await ws.getDenomInfo(
+ ws,
+ tx,
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ );
+ checkDbInvariant(!!denom);
+ coinPubs.push({
+ coinPub: c,
+ amount: denom?.value,
+ });
+ }
return await createRefreshGroup(
ws,
tx,