summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-09-14 20:34:37 +0200
committerFlorian Dold <florian@dold.me>2022-09-14 20:40:38 +0200
commitc021876b41bff11ad28c3a43808795fa0d02ce99 (patch)
treec92f4e83def462ddb0d446c9c476fd32f648d744 /packages/taler-wallet-core/src/operations
parent9d044058e267e9e94f3ee63809a1e22426151ee5 (diff)
downloadwallet-core-c021876b41bff11ad28c3a43808795fa0d02ce99.tar.gz
wallet-core-c021876b41bff11ad28c3a43808795fa0d02ce99.tar.bz2
wallet-core-c021876b41bff11ad28c3a43808795fa0d02ce99.zip
wallet-core: cache fresh coin count in DB
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts1
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts20
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts77
-rw-r--r--packages/taler-wallet-core/src/operations/peer-to-peer.ts46
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts23
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts7
7 files changed, 61 insertions, 119 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index 8f5d019d4..53e45918e 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -413,7 +413,6 @@ export async function importBackup(
currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
denomSig: backupCoin.denom_sig,
coinPub: compCoin.coinPub,
- suspended: false,
exchangeBaseUrl: backupExchangeDetails.base_url,
denomPubHash,
status: backupCoin.fresh
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts
index 5838be765..6d63def59 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -33,12 +33,11 @@ import {
getRandomBytes,
hashWire,
Logger,
- NotificationType,
parsePaytoUri,
PayCoinSelection,
PrepareDepositRequest,
PrepareDepositResponse,
- TalerErrorDetail,
+ RefreshReason,
TalerProtocolTimestamp,
TrackDepositGroupRequest,
TrackDepositGroupResponse,
@@ -46,18 +45,15 @@ import {
} from "@gnu-taler/taler-util";
import {
DepositGroupRecord,
- OperationAttemptErrorResult,
OperationAttemptResult,
OperationStatus,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { selectPayCoins } from "../util/coinSelection.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
-import { RetryInfo } from "../util/retries.js";
-import { guardOperationException } from "./common.js";
+import { spendCoins } from "../wallet.js";
import { getExchangeDetails } from "./exchanges.js";
import {
- applyCoinSpend,
CoinSelectionRequest,
extractContractData,
generateDepositPermissions,
@@ -525,12 +521,12 @@ export async function createDepositGroup(
x.refreshGroups,
])
.runReadWrite(async (tx) => {
- await applyCoinSpend(
- ws,
- tx,
- payCoinSel,
- `deposit-group:${depositGroup.depositGroupId}`,
- );
+ await spendCoins(ws, tx, {
+ allocationId: `deposit-group:${depositGroup.depositGroupId}`,
+ coinPubs: payCoinSel.coinPubs,
+ contributions: payCoinSel.coinContributions,
+ refreshReason: RefreshReason.PayDeposit,
+ });
await tx.depositGroups.put(depositGroup);
});
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index 322e90487..bd7b1f7f0 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -100,6 +100,7 @@ import {
} from "../util/http.js";
import { GetReadWriteAccess } from "../util/query.js";
import { RetryInfo, RetryTags, scheduleRetry } from "../util/retries.js";
+import { spendCoins } from "../wallet.js";
import { getExchangeDetails } from "./exchanges.js";
import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
@@ -156,9 +157,6 @@ export async function getTotalPaymentCost(
}
function isSpendableCoin(coin: CoinRecord, denom: DenominationRecord): boolean {
- if (coin.suspended) {
- return false;
- }
if (denom.isRevoked) {
return false;
}
@@ -348,65 +346,6 @@ export async function getCandidatePayCoins(
}
/**
- * Apply a coin selection to the database. Marks coins as spent
- * and creates a refresh session for the remaining amount.
- *
- * FIXME: This does not deal well with conflicting spends!
- * When two payments are made in parallel, the same coin can be selected
- * for two payments.
- * However, this is a situation that can also happen via sync.
- */
-export async function applyCoinSpend(
- ws: InternalWalletState,
- tx: GetReadWriteAccess<{
- coins: typeof WalletStoresV1.coins;
- refreshGroups: typeof WalletStoresV1.refreshGroups;
- denominations: typeof WalletStoresV1.denominations;
- }>,
- coinSelection: PayCoinSelection,
- allocationId: string,
-): Promise<void> {
- logger.info(`applying coin spend ${j2s(coinSelection)}`);
- for (let i = 0; i < coinSelection.coinPubs.length; i++) {
- const coin = await tx.coins.get(coinSelection.coinPubs[i]);
- if (!coin) {
- throw Error("coin allocated for payment doesn't exist anymore");
- }
- const contrib = coinSelection.coinContributions[i];
- if (coin.status !== CoinStatus.Fresh) {
- const alloc = coin.allocation;
- if (!alloc) {
- continue;
- }
- if (alloc.id !== allocationId) {
- // FIXME: assign error code
- throw Error("conflicting coin allocation (id)");
- }
- if (0 !== Amounts.cmp(alloc.amount, contrib)) {
- // FIXME: assign error code
- throw Error("conflicting coin allocation (contrib)");
- }
- continue;
- }
- coin.status = CoinStatus.Dormant;
- coin.allocation = {
- id: allocationId,
- amount: Amounts.stringify(contrib),
- };
- const remaining = Amounts.sub(coin.currentAmount, contrib);
- if (remaining.saturated) {
- throw Error("not enough remaining balance on coin for payment");
- }
- coin.currentAmount = remaining.amount;
- await tx.coins.put(coin);
- }
- const refreshCoinPubs = coinSelection.coinPubs.map((x) => ({
- coinPub: x,
- }));
- await createRefreshGroup(ws, tx, refreshCoinPubs, RefreshReason.PayMerchant);
-}
-
-/**
* Record all information that is necessary to
* pay for a proposal in the wallet's database.
*/
@@ -468,7 +407,12 @@ async function recordConfirmPay(
await tx.proposals.put(p);
}
await tx.purchases.put(t);
- await applyCoinSpend(ws, tx, coinSelection, `proposal:${t.proposalId}`);
+ await spendCoins(ws, tx, {
+ allocationId: `proposal:${t.proposalId}`,
+ coinPubs: coinSelection.coinPubs,
+ contributions: coinSelection.coinContributions,
+ refreshReason: RefreshReason.PayMerchant,
+ });
});
ws.notify({
@@ -1038,7 +982,12 @@ async function handleInsufficientFunds(
p.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
p.coinDepositPermissions = undefined;
await tx.purchases.put(p);
- await applyCoinSpend(ws, tx, res, `proposal:${p.proposalId}`);
+ await spendCoins(ws, tx, {
+ allocationId: `proposal:${p.proposalId}`,
+ coinPubs: p.payCoinSelection.coinPubs,
+ contributions: p.payCoinSelection.coinContributions,
+ refreshReason: RefreshReason.PayMerchant,
+ });
});
}
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 59dad3d55..449a91c68 100644
--- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts
+++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
@@ -75,6 +75,8 @@ import { internalCreateWithdrawalGroup } from "./withdraw.js";
import { GetReadOnlyAccess } from "../util/query.js";
import { createRefreshGroup } from "./refresh.js";
import { updateExchangeFromUrl } from "./exchanges.js";
+import { spendCoins } from "../wallet.js";
+import { RetryTags } from "../util/retries.js";
const logger = new Logger("operations/peer-to-peer.ts");
@@ -256,18 +258,14 @@ export async function initiatePeerToPeerPush(
return undefined;
}
- const pubs: CoinPublicKey[] = [];
- for (const c of sel.coins) {
- const coin = await tx.coins.get(c.coinPub);
- checkDbInvariant(!!coin);
- coin.currentAmount = Amounts.sub(
- coin.currentAmount,
- Amounts.parseOrThrow(c.contribution),
- ).amount;
- coin.status = CoinStatus.Dormant;
- pubs.push({ coinPub: coin.coinPub });
- await tx.coins.put(coin);
- }
+ await spendCoins(ws, tx, {
+ allocationId: `peer-push:${pursePair.pub}`,
+ coinPubs: sel.coins.map((x) => x.coinPub),
+ contributions: sel.coins.map((x) =>
+ Amounts.parseOrThrow(x.contribution),
+ ),
+ refreshReason: RefreshReason.PayPeerPush,
+ });
await tx.peerPushPaymentInitiations.add({
amount: Amounts.stringify(instructedAmount),
@@ -284,8 +282,6 @@ export async function initiatePeerToPeerPush(
timestampCreated: TalerProtocolTimestamp.now(),
});
- await createRefreshGroup(ws, tx, pubs, RefreshReason.PayPeerPush);
-
return sel;
});
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
@@ -588,20 +584,14 @@ export async function acceptPeerPullPayment(
return undefined;
}
- const pubs: CoinPublicKey[] = [];
- for (const c of sel.coins) {
- const coin = await tx.coins.get(c.coinPub);
- checkDbInvariant(!!coin);
- coin.currentAmount = Amounts.sub(
- coin.currentAmount,
- Amounts.parseOrThrow(c.contribution),
- ).amount;
- coin.status = CoinStatus.Dormant;
- pubs.push({ coinPub: coin.coinPub });
- await tx.coins.put(coin);
- }
-
- await createRefreshGroup(ws, tx, pubs, RefreshReason.PayPeerPull);
+ await spendCoins(ws, tx, {
+ allocationId: `peer-pull:${req.peerPullPaymentIncomingId}`,
+ coinPubs: sel.coins.map((x) => x.coinPub),
+ contributions: sel.coins.map((x) =>
+ Amounts.parseOrThrow(x.contribution),
+ ),
+ refreshReason: RefreshReason.PayPeerPull,
+ });
const pi = await tx.peerPullPaymentIncoming.get(
req.peerPullPaymentIncomingId,
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 719093bd8..d1c366cd0 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -77,6 +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 { guardOperationException } from "./common.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import {
@@ -670,7 +671,6 @@ async function refreshReveal(
type: CoinSourceType.Refresh,
oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
},
- suspended: false,
coinEvHash: pc.coinEvHash,
ageCommitmentProof: pc.ageCommitmentProof,
};
@@ -680,7 +680,7 @@ async function refreshReveal(
}
await ws.db
- .mktx((x) => [x.coins, x.refreshGroups])
+ .mktx((x) => [x.coins, x.denominations, x.refreshGroups])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
if (!rg) {
@@ -694,7 +694,7 @@ async function refreshReveal(
rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
updateGroupStatus(rg);
for (const coin of coins) {
- await tx.coins.put(coin);
+ await makeCoinAvailable(ws, tx, coin);
}
await tx.refreshGroups.put(rg);
});
@@ -865,10 +865,22 @@ export async function createRefreshGroup(
!!denom,
"denomination for existing coin must be in database",
);
+ if (coin.status !== CoinStatus.Dormant) {
+ coin.status = CoinStatus.Dormant;
+ const denom = await tx.denominations.get([
+ coin.exchangeBaseUrl,
+ coin.denomPubHash,
+ ]);
+ checkDbInvariant(!!denom);
+ checkDbInvariant(
+ denom.freshCoinCount != null && denom.freshCoinCount > 0,
+ );
+ denom.freshCoinCount--;
+ await tx.denominations.put(denom);
+ }
const refreshAmount = coin.currentAmount;
inputPerCoin.push(refreshAmount);
coin.currentAmount = Amounts.getZero(refreshAmount.currency);
- coin.status = CoinStatus.Dormant;
await tx.coins.put(coin);
const denoms = await getDenoms(coin.exchangeBaseUrl);
const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
@@ -965,9 +977,6 @@ export async function autoRefresh(
if (coin.status !== CoinStatus.Fresh) {
continue;
}
- if (coin.suspended) {
- continue;
- }
const denom = await tx.denominations.get([
exchangeBaseUrl,
coin.denomPubHash,
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts
index 04da2b988..f70e2d02b 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -51,6 +51,7 @@ import {
readSuccessResponseJsonOrThrow,
} from "../util/http.js";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
+import { makeCoinAvailable } from "../wallet.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import {
getCandidateWithdrawalDenoms,
@@ -310,13 +311,12 @@ export async function processTip(
denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
status: CoinStatus.Fresh,
- suspended: false,
coinEvHash: planchet.coinEvHash,
});
}
await ws.db
- .mktx((x) => [x.coins, x.tips, x.withdrawalGroups])
+ .mktx((x) => [x.coins, x.denominations, x.tips])
.runReadWrite(async (tx) => {
const tr = await tx.tips.get(walletTipId);
if (!tr) {
@@ -328,7 +328,7 @@ export async function processTip(
tr.pickedUpTimestamp = TalerProtocolTimestamp.now();
await tx.tips.put(tr);
for (const cr of newCoinRecords) {
- await tx.coins.put(cr);
+ await makeCoinAvailable(ws, tx, cr);
}
});
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 1b8383776..bee83265c 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -93,11 +93,11 @@ import {
} from "../util/http.js";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
-import { RetryInfo } from "../util/retries.js";
import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION,
} from "../versions.js";
+import { makeCoinAvailable } from "../wallet.js";
import {
getExchangeDetails,
getExchangePaytoUri,
@@ -805,7 +805,6 @@ async function processPlanchetVerifyAndStoreCoin(
reservePub: planchet.reservePub,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
},
- suspended: false,
ageCommitmentProof: planchet.ageCommitmentProof,
};
@@ -815,7 +814,7 @@ async function processPlanchetVerifyAndStoreCoin(
// withdrawal succeeded. If so, mark the withdrawal
// group as finished.
const firstSuccess = await ws.db
- .mktx((x) => [x.coins, x.withdrawalGroups, x.planchets])
+ .mktx((x) => [x.coins, x.denominations, x.withdrawalGroups, x.planchets])
.runReadWrite(async (tx) => {
const p = await tx.planchets.get(planchetCoinPub);
if (!p || p.withdrawalDone) {
@@ -823,7 +822,7 @@ async function processPlanchetVerifyAndStoreCoin(
}
p.withdrawalDone = true;
await tx.planchets.put(p);
- await tx.coins.add(coin);
+ await makeCoinAvailable(ws, tx, coin);
return true;
});