aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-09-16 16:20:47 +0200
committerFlorian Dold <florian@dold.me>2022-09-16 16:32:21 +0200
commitb91caf977fad8da11e523ca3a39064dd86e04c64 (patch)
tree732e1543d2555094d7f9a9ca242309847c1a33a3 /packages/taler-wallet-core/src/operations
parent2747bc260bc05418974570d04d7f999dfc988cda (diff)
downloadwallet-core-b91caf977fad8da11e523ca3a39064dd86e04c64.tar.xz
wallet-core: support age restrictions in new coin selection
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts60
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts273
-rw-r--r--packages/taler-wallet-core/src/operations/peer-to-peer.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/recoup.ts8
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts34
-rw-r--r--packages/taler-wallet-core/src/operations/refund.ts8
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts4
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts11
9 files changed, 180 insertions, 227 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index 53dc50f3b..be09952cd 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -15,6 +15,7 @@
*/
import {
+ AgeRestriction,
AmountJson,
Amounts,
BackupCoinSourceType,
@@ -436,6 +437,8 @@ export async function importBackup(
? CoinStatus.Fresh
: CoinStatus.Dormant,
coinSource,
+ // FIXME!
+ maxAge: AgeRestriction.AGE_UNRESTRICTED,
});
}
}
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts
index 9747f21a3..22ec5f0a5 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -51,16 +51,14 @@ import {
OperationStatus,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
-import { selectPayCoinsLegacy } from "../util/coinSelection.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { spendCoins } from "../wallet.js";
import { getExchangeDetails } from "./exchanges.js";
import {
- CoinSelectionRequest,
extractContractData,
generateDepositPermissions,
- getCandidatePayCoins,
getTotalPaymentCost,
+ selectPayCoinsNew,
} from "./pay.js";
import { getTotalRefreshCost } from "./refresh.js";
import { makeEventId } from "./transactions.js";
@@ -255,28 +253,17 @@ export async function getFeeForDeposit(
}
});
- const csr: CoinSelectionRequest = {
- allowedAuditors: [],
- allowedExchanges: Object.values(exchangeInfos).map((v) => ({
+ const payCoinSel = await selectPayCoinsNew(ws, {
+ auditors: [],
+ exchanges: Object.values(exchangeInfos).map((v) => ({
exchangeBaseUrl: v.url,
exchangePub: v.master_pub,
})),
- amount: Amounts.parseOrThrow(req.amount),
- maxDepositFee: Amounts.parseOrThrow(req.amount),
- maxWireFee: Amounts.parseOrThrow(req.amount),
- timestamp: TalerProtocolTimestamp.now(),
- wireFeeAmortization: 1,
wireMethod: p.targetType,
- };
-
- const candidates = await getCandidatePayCoins(ws, csr);
-
- const payCoinSel = selectPayCoinsLegacy({
- candidates,
- contractTermsAmount: csr.amount,
- depositFeeLimit: csr.maxDepositFee,
- wireFeeAmortization: csr.wireFeeAmortization,
- wireFeeLimit: csr.maxWireFee,
+ contractTermsAmount: Amounts.parseOrThrow(req.amount),
+ depositFeeLimit: Amounts.parseOrThrow(req.amount),
+ wireFeeAmortization: 1,
+ wireFeeLimit: Amounts.parseOrThrow(req.amount),
prevPayCoins: [],
});
@@ -356,19 +343,10 @@ export async function prepareDepositGroup(
"",
);
- const candidates = await getCandidatePayCoins(ws, {
- allowedAuditors: contractData.allowedAuditors,
- allowedExchanges: contractData.allowedExchanges,
- amount: contractData.amount,
- maxDepositFee: contractData.maxDepositFee,
- maxWireFee: contractData.maxWireFee,
- timestamp: contractData.timestamp,
- wireFeeAmortization: contractData.wireFeeAmortization,
+ const payCoinSel = await selectPayCoinsNew(ws, {
+ auditors: contractData.allowedAuditors,
+ exchanges: contractData.allowedExchanges,
wireMethod: contractData.wireMethod,
- });
-
- const payCoinSel = selectPayCoinsLegacy({
- candidates,
contractTermsAmount: contractData.amount,
depositFeeLimit: contractData.maxDepositFee,
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
@@ -459,19 +437,10 @@ export async function createDepositGroup(
"",
);
- const candidates = await getCandidatePayCoins(ws, {
- allowedAuditors: contractData.allowedAuditors,
- allowedExchanges: contractData.allowedExchanges,
- amount: contractData.amount,
- maxDepositFee: contractData.maxDepositFee,
- maxWireFee: contractData.maxWireFee,
- timestamp: contractData.timestamp,
- wireFeeAmortization: contractData.wireFeeAmortization,
+ const payCoinSel = await selectPayCoinsNew(ws, {
+ auditors: contractData.allowedAuditors,
+ exchanges: contractData.allowedExchanges,
wireMethod: contractData.wireMethod,
- });
-
- const payCoinSel = selectPayCoinsLegacy({
- candidates,
contractTermsAmount: contractData.amount,
depositFeeLimit: contractData.maxDepositFee,
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
@@ -522,6 +491,7 @@ export async function createDepositGroup(
x.recoupGroups,
x.denominations,
x.refreshGroups,
+ x.coinAvailability,
])
.runReadWrite(async (tx) => {
await spendCoins(ws, tx, {
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index af6ff507f..ab59fff87 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -24,6 +24,7 @@
/**
* Imports.
*/
+import { BridgeIDBKeyRange, GlobalIDB } from "@gnu-taler/idb-bridge";
import {
AbsoluteTime,
AgeRestriction,
@@ -102,7 +103,7 @@ import {
readUnexpectedResponseDetails,
throwUnexpectedRequestError,
} from "../util/http.js";
-import { checkLogicInvariant } from "../util/invariants.js";
+import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js";
import { RetryInfo, RetryTags, scheduleRetry } from "../util/retries.js";
import { spendCoins } from "../wallet.js";
@@ -216,149 +217,6 @@ export interface CoinSelectionRequest {
}
/**
- * Get candidate coins. From these candidate coins,
- * the actual contributions will be computed later.
- *
- * The resulting candidate coin list is sorted deterministically.
- *
- * TODO: Exclude more coins:
- * - when we already have a coin with more remaining amount than
- * the payment amount, coins with even higher amounts can be skipped.
- */
-export async function getCandidatePayCoins(
- ws: InternalWalletState,
- req: CoinSelectionRequest,
-): Promise<CoinCandidateSelection> {
- const candidateCoins: AvailableCoinInfo[] = [];
- const wireFeesPerExchange: Record<string, AmountJson> = {};
-
- await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations, x.coins])
- .runReadOnly(async (tx) => {
- const exchanges = await tx.exchanges.iter().toArray();
- for (const exchange of exchanges) {
- let isOkay = false;
- const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
- if (!exchangeDetails) {
- continue;
- }
- const exchangeFees = exchangeDetails.wireInfo;
- if (!exchangeFees) {
- continue;
- }
-
- const wireTypes = new Set<string>();
- for (const acc of exchangeDetails.wireInfo.accounts) {
- const p = parsePaytoUri(acc.payto_uri);
- if (p) {
- wireTypes.add(p.targetType);
- }
- }
-
- if (!wireTypes.has(req.wireMethod)) {
- // Exchange can't be used, because it doesn't support
- // the wire type that the merchant requested.
- continue;
- }
-
- // is the exchange explicitly allowed?
- for (const allowedExchange of req.allowedExchanges) {
- if (allowedExchange.exchangePub === exchangeDetails.masterPublicKey) {
- isOkay = true;
- break;
- }
- }
-
- // is the exchange allowed because of one of its auditors?
- if (!isOkay) {
- for (const allowedAuditor of req.allowedAuditors) {
- for (const auditor of exchangeDetails.auditors) {
- if (auditor.auditor_pub === allowedAuditor.auditorPub) {
- isOkay = true;
- break;
- }
- }
- if (isOkay) {
- break;
- }
- }
- }
-
- if (!isOkay) {
- continue;
- }
-
- const coins = await tx.coins.indexes.byBaseUrl
- .iter(exchange.baseUrl)
- .toArray();
-
- if (!coins || coins.length === 0) {
- continue;
- }
-
- // Denomination of the first coin, we assume that all other
- // coins have the same currency
- const firstDenom = await ws.getDenomInfo(
- ws,
- tx,
- exchange.baseUrl,
- coins[0].denomPubHash,
- );
- if (!firstDenom) {
- throw Error("db inconsistent");
- }
- const currency = firstDenom.value.currency;
- for (const coin of coins) {
- const denom = await tx.denominations.get([
- exchange.baseUrl,
- coin.denomPubHash,
- ]);
- if (!denom) {
- throw Error("db inconsistent");
- }
- if (denom.currency !== currency) {
- logger.warn(
- `same pubkey for different currencies at exchange ${exchange.baseUrl}`,
- );
- continue;
- }
- if (!isSpendableCoin(coin, denom)) {
- continue;
- }
- candidateCoins.push({
- availableAmount: coin.currentAmount,
- value: DenominationRecord.getValue(denom),
- coinPub: coin.coinPub,
- denomPub: denom.denomPub,
- feeDeposit: denom.fees.feeDeposit,
- exchangeBaseUrl: denom.exchangeBaseUrl,
- ageCommitmentProof: coin.ageCommitmentProof,
- });
- }
-
- let wireFee: AmountJson | undefined;
- for (const fee of exchangeFees.feesForType[req.wireMethod] || []) {
- if (
- fee.startStamp <= req.timestamp &&
- fee.endStamp >= req.timestamp
- ) {
- wireFee = fee.wireFee;
- break;
- }
- }
- if (wireFee) {
- wireFeesPerExchange[exchange.baseUrl] = wireFee;
- }
- }
- });
-
- return {
- candidateCoins,
- wireFeesPerExchange,
- };
-}
-
-/**
* Record all information that is necessary to
* pay for a proposal in the wallet's database.
*/
@@ -412,6 +270,7 @@ async function recordConfirmPay(
x.coins,
x.refreshGroups,
x.denominations,
+ x.coinAvailability,
])
.runReadWrite(async (tx) => {
const p = await tx.proposals.get(proposal.proposalId);
@@ -976,7 +835,13 @@ async function handleInsufficientFunds(
logger.trace("re-selected coins");
await ws.db
- .mktx((x) => [x.purchases, x.coins, x.denominations, x.refreshGroups])
+ .mktx((x) => [
+ x.purchases,
+ x.coins,
+ x.coinAvailability,
+ x.denominations,
+ x.refreshGroups,
+ ])
.runReadWrite(async (tx) => {
const p = await tx.purchases.get(proposalId);
if (!p) {
@@ -1029,6 +894,7 @@ export interface SelectPayCoinRequestNg {
}
export type AvailableDenom = DenominationInfo & {
+ maxAge: number;
numAvailable: number;
};
@@ -1037,7 +903,12 @@ async function selectCandidates(
req: SelectPayCoinRequestNg,
): Promise<[AvailableDenom[], Record<string, AmountJson>]> {
return await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+ .mktx((x) => [
+ x.exchanges,
+ x.exchangeDetails,
+ x.denominations,
+ x.coinAvailability,
+ ])
.runReadOnly(async (tx) => {
const denoms: AvailableDenom[] = [];
const exchanges = await tx.exchanges.iter().toArray();
@@ -1065,17 +936,35 @@ async function selectCandidates(
if (!accepted) {
continue;
}
- // FIXME: Do this query more efficiently via indexing
- const exchangeDenoms = await tx.denominations.indexes.byExchangeBaseUrl
- .iter(exchangeDetails.exchangeBaseUrl)
- .filter((x) => x.freshCoinCount != null && x.freshCoinCount > 0);
+ let ageLower = 0;
+ let ageUpper = Number.MAX_SAFE_INTEGER;
+ if (req.requiredMinimumAge) {
+ ageLower = req.requiredMinimumAge;
+ }
+ const myExchangeDenoms =
+ await tx.coinAvailability.indexes.byExchangeAgeAvailability.getAll(
+ GlobalIDB.KeyRange.bound(
+ [exchangeDetails.exchangeBaseUrl, ageLower, 1],
+ [
+ exchangeDetails.exchangeBaseUrl,
+ ageUpper,
+ Number.MAX_SAFE_INTEGER,
+ ],
+ ),
+ );
// FIXME: Check that the individual denomination is audited!
// FIXME: Should we exclude denominations that are
// not spendable anymore?
- for (const denom of exchangeDenoms) {
+ for (const denomAvail of myExchangeDenoms) {
+ const denom = await tx.denominations.get([
+ denomAvail.exchangeBaseUrl,
+ denomAvail.denomPubHash,
+ ]);
+ checkDbInvariant(!!denom);
denoms.push({
...DenominationRecord.toDenomInfo(denom),
- numAvailable: denom.freshCoinCount ?? 0,
+ numAvailable: denomAvail.freshCoinCount ?? 0,
+ maxAge: denomAvail.maxAge,
});
}
}
@@ -1092,15 +981,28 @@ async function selectCandidates(
});
}
+function makeAvailabilityKey(
+ exchangeBaseUrl: string,
+ denomPubHash: string,
+ maxAge: number,
+): string {
+ return `${denomPubHash};${maxAge};${exchangeBaseUrl}`;
+}
+
/**
* Selection result.
*/
interface SelResult {
/**
- * Map from denomination public key hashes
+ * Map from an availability key
* to an array of contributions.
*/
- [dph: string]: AmountJson[];
+ [avKey: string]: {
+ exchangeBaseUrl: string;
+ denomPubHash: string;
+ maxAge: number;
+ contributions: AmountJson[];
+ };
}
export function selectGreedy(
@@ -1146,7 +1048,22 @@ export function selectGreedy(
}
if (contributions.length) {
- selectedDenom[aci.denomPubHash] = contributions;
+ const avKey = makeAvailabilityKey(
+ aci.exchangeBaseUrl,
+ aci.denomPubHash,
+ aci.maxAge,
+ );
+ let sd = selectedDenom[avKey];
+ if (!sd) {
+ sd = {
+ contributions: [],
+ denomPubHash: aci.denomPubHash,
+ exchangeBaseUrl: aci.exchangeBaseUrl,
+ maxAge: aci.maxAge,
+ };
+ }
+ sd.contributions.push(...contributions);
+ selectedDenom[avKey] = sd;
}
if (Amounts.isZero(tally.amountPayRemaining)) {
@@ -1173,9 +1090,22 @@ export function selectForced(
}
if (Amounts.cmp(aci.value, forcedCoin.value) === 0) {
aci.numAvailable--;
- const contributions = selectedDenom[aci.denomPubHash] ?? [];
- contributions.push(Amounts.parseOrThrow(forcedCoin.value));
- selectedDenom[aci.denomPubHash] = contributions;
+ const avKey = makeAvailabilityKey(
+ aci.exchangeBaseUrl,
+ aci.denomPubHash,
+ aci.maxAge,
+ );
+ let sd = selectedDenom[avKey];
+ if (!sd) {
+ sd = {
+ contributions: [],
+ denomPubHash: aci.denomPubHash,
+ exchangeBaseUrl: aci.exchangeBaseUrl,
+ maxAge: aci.maxAge,
+ };
+ }
+ sd.contributions.push(Amounts.parseOrThrow(forcedCoin.value));
+ selectedDenom[avKey] = sd;
found = true;
break;
}
@@ -1273,18 +1203,27 @@ export async function selectPayCoinsNew(
.mktx((x) => [x.coins, x.denominations])
.runReadOnly(async (tx) => {
for (const dph of Object.keys(finalSel)) {
- const contributions = finalSel[dph];
- const coins = await tx.coins.indexes.byDenomPubHashAndStatus.getAll(
- [dph, CoinStatus.Fresh],
- contributions.length,
- );
- if (coins.length != contributions.length) {
+ const selInfo = finalSel[dph];
+ const numRequested = selInfo.contributions.length;
+ const query = [
+ selInfo.exchangeBaseUrl,
+ selInfo.denomPubHash,
+ selInfo.maxAge,
+ CoinStatus.Fresh,
+ ];
+ logger.info(`query: ${j2s(query)}`);
+ const coins =
+ await tx.coins.indexes.byExchangeDenomPubHashAndAgeAndStatus.getAll(
+ query,
+ numRequested,
+ );
+ if (coins.length != numRequested) {
throw Error(
- `coin selection failed (not available anymore, got only ${coins.length}/${contributions.length})`,
+ `coin selection failed (not available anymore, got only ${coins.length}/${numRequested})`,
);
}
coinPubs.push(...coins.map((x) => x.coinPub));
- coinContributions.push(...contributions);
+ coinContributions.push(...selInfo.contributions);
}
});
@@ -1535,7 +1474,7 @@ export async function generateDepositPermissions(
let wireInfoHash: string;
wireInfoHash = contractData.wireInfoHash;
logger.trace(
- `signing deposit permission for coin with acp=${j2s(
+ `signing deposit permission for coin with ageRestriction=${j2s(
coin.ageCommitmentProof,
)}`,
);
diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
index e71e8a709..ffbc1fc97 100644
--- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts
+++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
@@ -118,7 +118,8 @@ interface CoinInfo {
denomSig: UnblindedSignature;
- ageCommitmentProof: AgeCommitmentProof | undefined;
+ maxAge: number;
+ ageCommitmentProof?: AgeCommitmentProof;
}
export async function selectPeerCoins(
@@ -156,6 +157,7 @@ export async function selectPeerCoins(
denomPubHash: denom.denomPubHash,
coinPriv: coin.coinPriv,
denomSig: coin.denomSig,
+ maxAge: coin.maxAge,
ageCommitmentProof: coin.ageCommitmentProof,
});
}
@@ -245,6 +247,7 @@ export async function initiatePeerToPeerPush(
.mktx((x) => [
x.exchanges,
x.coins,
+ x.coinAvailability,
x.denominations,
x.refreshGroups,
x.peerPullPaymentInitiations,
@@ -583,6 +586,7 @@ export async function acceptPeerPullPayment(
x.denominations,
x.refreshGroups,
x.peerPullPaymentIncoming,
+ x.coinAvailability,
])
.runReadWrite(async (tx) => {
const sel = await selectPeerCoins(ws, tx, instructedAmount);
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts
index 100bbc074..bd598511a 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -392,7 +392,13 @@ export async function processRecoupGroupHandler(
}
await ws.db
- .mktx((x) => [x.recoupGroups, x.denominations, x.refreshGroups, x.coins])
+ .mktx((x) => [
+ x.recoupGroups,
+ x.coinAvailability,
+ x.denominations,
+ x.refreshGroups,
+ x.coins,
+ ])
.runReadWrite(async (tx) => {
const rg2 = await tx.recoupGroups.get(recoupGroupId);
if (!rg2) {
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 2d9ad2c05..e968ec020 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -77,7 +77,7 @@ import {
import { checkDbInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js";
import { RetryInfo, runOperationHandlerForResult } from "../util/retries.js";
-import { makeCoinAvailable } from "../wallet.js";
+import { makeCoinAvailable, Wallet } from "../wallet.js";
import { guardOperationException } from "./common.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import {
@@ -368,6 +368,7 @@ async function refreshMelt(
meltCoinPriv: oldCoin.coinPriv,
meltCoinPub: oldCoin.coinPub,
feeRefresh: oldDenom.feeRefresh,
+ meltCoinMaxAge: oldCoin.maxAge,
meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
newCoinDenoms,
sessionSecretSeed: refreshSession.sessionSecretSeed,
@@ -614,6 +615,7 @@ async function refreshReveal(
meltCoinPub: oldCoin.coinPub,
feeRefresh: oldDenom.feeRefresh,
newCoinDenoms,
+ meltCoinMaxAge: oldCoin.maxAge,
meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
sessionSecretSeed: refreshSession.sessionSecretSeed,
});
@@ -676,6 +678,7 @@ async function refreshReveal(
oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
},
coinEvHash: pc.coinEvHash,
+ maxAge: pc.maxAge,
ageCommitmentProof: pc.ageCommitmentProof,
};
@@ -684,7 +687,12 @@ async function refreshReveal(
}
await ws.db
- .mktx((x) => [x.coins, x.denominations, x.refreshGroups])
+ .mktx((x) => [
+ x.coins,
+ x.denominations,
+ x.coinAvailability,
+ x.refreshGroups,
+ ])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
if (!rg) {
@@ -830,6 +838,7 @@ export async function createRefreshGroup(
denominations: typeof WalletStoresV1.denominations;
coins: typeof WalletStoresV1.coins;
refreshGroups: typeof WalletStoresV1.refreshGroups;
+ coinAvailability: typeof WalletStoresV1.coinAvailability;
}>,
oldCoinPubs: CoinPublicKey[],
reason: RefreshReason,
@@ -871,16 +880,15 @@ export async function createRefreshGroup(
);
if (coin.status !== CoinStatus.Dormant) {
coin.status = CoinStatus.Dormant;
- const denom = await tx.denominations.get([
+ const coinAv = await tx.coinAvailability.get([
coin.exchangeBaseUrl,
coin.denomPubHash,
+ coin.maxAge,
]);
- checkDbInvariant(!!denom);
- checkDbInvariant(
- denom.freshCoinCount != null && denom.freshCoinCount > 0,
- );
- denom.freshCoinCount--;
- await tx.denominations.put(denom);
+ checkDbInvariant(!!coinAv);
+ checkDbInvariant(coinAv.freshCoinCount > 0);
+ coinAv.freshCoinCount--;
+ await tx.coinAvailability.put(coinAv);
}
const refreshAmount = coin.currentAmount;
inputPerCoin.push(refreshAmount);
@@ -967,7 +975,13 @@ export async function autoRefresh(
durationFromSpec({ days: 1 }),
);
await ws.db
- .mktx((x) => [x.coins, x.denominations, x.refreshGroups, x.exchanges])
+ .mktx((x) => [
+ x.coins,
+ x.denominations,
+ x.coinAvailability,
+ x.refreshGroups,
+ x.exchanges,
+ ])
.runReadWrite(async (tx) => {
const exchange = await tx.exchanges.get(exchangeBaseUrl);
if (!exchange) {
diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts
index 644b07ef1..bdcdac943 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -336,7 +336,13 @@ async function acceptRefunds(
const now = TalerProtocolTimestamp.now();
await ws.db
- .mktx((x) => [x.purchases, x.coins, x.denominations, x.refreshGroups])
+ .mktx((x) => [
+ x.purchases,
+ x.coins,
+ x.coinAvailability,
+ x.denominations,
+ x.refreshGroups,
+ ])
.runReadWrite(async (tx) => {
const p = await tx.purchases.get(proposalId);
if (!p) {
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts
index eef151cf2..9f96b7a7d 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -18,6 +18,7 @@
* Imports.
*/
import {
+ AgeRestriction,
AcceptTipResponse,
Amounts,
BlindedDenominationSignature,
@@ -315,11 +316,12 @@ export async function processTip(
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
status: CoinStatus.Fresh,
coinEvHash: planchet.coinEvHash,
+ maxAge: AgeRestriction.AGE_UNRESTRICTED,
});
}
await ws.db
- .mktx((x) => [x.coins, x.denominations, x.tips])
+ .mktx((x) => [x.coins, x.coinAvailability, x.denominations, x.tips])
.runReadWrite(async (tx) => {
const tr = await tx.tips.get(walletTipId);
if (!tr) {
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index f2152ccbc..cb0b55faf 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -22,6 +22,7 @@ import {
AcceptManualWithdrawalResult,
AcceptWithdrawalResponse,
addPaytoQueryParams,
+ AgeRestriction,
AmountJson,
AmountLike,
Amounts,
@@ -510,6 +511,7 @@ async function processPlanchetGenerate(
withdrawalDone: false,
withdrawSig: r.withdrawSig,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
+ maxAge: withdrawalGroup.restrictAge ?? AgeRestriction.AGE_UNRESTRICTED,
ageCommitmentProof: r.ageCommitmentProof,
lastError: undefined,
};
@@ -823,6 +825,7 @@ async function processPlanchetVerifyAndStoreCoin(
reservePub: planchet.reservePub,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
},
+ maxAge: planchet.maxAge,
ageCommitmentProof: planchet.ageCommitmentProof,
};
@@ -832,7 +835,13 @@ async function processPlanchetVerifyAndStoreCoin(
// withdrawal succeeded. If so, mark the withdrawal
// group as finished.
const firstSuccess = await ws.db
- .mktx((x) => [x.coins, x.denominations, x.withdrawalGroups, x.planchets])
+ .mktx((x) => [
+ x.coins,
+ x.denominations,
+ x.coinAvailability,
+ x.withdrawalGroups,
+ x.planchets,
+ ])
.runReadWrite(async (tx) => {
const p = await tx.planchets.get(planchetCoinPub);
if (!p || p.withdrawalDone) {