aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/refresh.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/refresh.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts134
1 files changed, 81 insertions, 53 deletions
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index fb356f0fc..75adbc860 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -76,7 +76,11 @@ import {
RefreshReasonDetails,
WalletStoresV1,
} from "../db.js";
-import { isWithdrawableDenom, PendingTaskType } from "../index.js";
+import {
+ isWithdrawableDenom,
+ PendingTaskType,
+ RefreshSessionRecord,
+} from "../index.js";
import {
EXCHANGE_COINS_LOCK,
InternalWalletState,
@@ -130,11 +134,7 @@ export function getTotalRefreshCost(
const resultingAmount = Amounts.add(
Amounts.zeroOfCurrency(withdrawAmount.currency),
...withdrawDenoms.selectedDenoms.map(
- (d) =>
- Amounts.mult(
- DenominationRecord.getValue(denomMap[d.denomPubHash]),
- d.count,
- ).amount,
+ (d) => Amounts.mult(denomMap[d.denomPubHash].value, d.count).amount,
),
).amount;
const totalCost = Amounts.sub(amountLeft, resultingAmount).amount;
@@ -170,18 +170,23 @@ function updateGroupStatus(rg: RefreshGroupRecord): { final: boolean } {
/**
* Create a refresh session for one particular coin inside a refresh group.
+ *
+ * If the session already exists, return the existing one.
+ *
+ * If the session doesn't need to be created (refresh group gone or session already
+ * finished), return undefined.
*/
-async function refreshCreateSession(
+async function provideRefreshSession(
ws: InternalWalletState,
refreshGroupId: string,
coinIndex: number,
-): Promise<void> {
+): Promise<RefreshSessionRecord | undefined> {
logger.trace(
`creating refresh session for coin ${coinIndex} in refresh group ${refreshGroupId}`,
);
const d = await ws.db
- .mktx((x) => [x.refreshGroups, x.coins])
+ .mktx((x) => [x.refreshGroups, x.coins, x.refreshSessions])
.runReadWrite(async (tx) => {
const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
if (!refreshGroup) {
@@ -192,21 +197,24 @@ async function refreshCreateSession(
) {
return;
}
- const existingRefreshSession =
- refreshGroup.refreshSessionPerCoin[coinIndex];
- if (existingRefreshSession) {
- return;
- }
+ const existingRefreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
const oldCoinPub = refreshGroup.oldCoinPubs[coinIndex];
const coin = await tx.coins.get(oldCoinPub);
if (!coin) {
throw Error("Can't refresh, coin not found");
}
- return { refreshGroup, coin };
+ return { refreshGroup, coin, existingRefreshSession };
});
if (!d) {
- return;
+ return undefined;
+ }
+
+ if (d.existingRefreshSession) {
+ return d.existingRefreshSession;
}
const { refreshGroup, coin } = d;
@@ -288,17 +296,23 @@ async function refreshCreateSession(
const sessionSecretSeed = encodeCrock(getRandomBytes(64));
// Store refresh session for this coin in the database.
- await ws.db
- .mktx((x) => [x.refreshGroups, x.coins])
+ const newSession = await ws.db
+ .mktx((x) => [x.refreshGroups, x.coins, x.refreshSessions])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
if (!rg) {
return;
}
- if (rg.refreshSessionPerCoin[coinIndex]) {
+ const existingSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
+ if (existingSession) {
return;
}
- rg.refreshSessionPerCoin[coinIndex] = {
+ const newSession: RefreshSessionRecord = {
+ coinIndex,
+ refreshGroupId,
norevealIndex: undefined,
sessionSecretSeed: sessionSecretSeed,
newDenoms: newCoinDenoms.selectedDenoms.map((x) => ({
@@ -307,11 +321,13 @@ async function refreshCreateSession(
})),
amountRefreshOutput: Amounts.stringify(newCoinDenoms.totalCoinValue),
};
- await tx.refreshGroups.put(rg);
+ await tx.refreshSessions.put(newSession);
+ return newSession;
});
logger.trace(
`created refresh session for coin #${coinIndex} in ${refreshGroupId}`,
);
+ return newSession;
}
function getRefreshRequestTimeout(rg: RefreshGroupRecord): Duration {
@@ -326,13 +342,16 @@ async function refreshMelt(
coinIndex: number,
): Promise<void> {
const d = await ws.db
- .mktx((x) => [x.refreshGroups, x.coins, x.denominations])
+ .mktx((x) => [x.refreshGroups, x.refreshSessions, x.coins, x.denominations])
.runReadWrite(async (tx) => {
const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
if (!refreshGroup) {
return;
}
- const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
+ const refreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
if (!refreshSession) {
return;
}
@@ -442,7 +461,12 @@ async function refreshMelt(
if (resp.status === HttpStatusCode.NotFound) {
const errDetails = await readUnexpectedResponseDetails(resp);
const transitionInfo = await ws.db
- .mktx((x) => [x.refreshGroups, x.coins, x.coinAvailability])
+ .mktx((x) => [
+ x.refreshGroups,
+ x.refreshSessions,
+ x.coins,
+ x.coinAvailability,
+ ])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
if (!rg) {
@@ -456,12 +480,22 @@ async function refreshMelt(
}
const oldTxState = computeRefreshTransactionState(rg);
rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Failed;
- rg.lastErrorPerCoin[coinIndex] = errDetails;
+ const refreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
+ if (!refreshSession) {
+ throw Error(
+ "db invariant failed: missing refresh session in database",
+ );
+ }
+ refreshSession.lastError = errDetails;
const updateRes = updateGroupStatus(rg);
if (updateRes.final) {
await makeCoinsVisible(ws, tx, transactionId);
}
await tx.refreshGroups.put(rg);
+ await tx.refreshSessions.put(refreshSession);
const newTxState = computeRefreshTransactionState(rg);
return {
oldTxState,
@@ -493,7 +527,7 @@ async function refreshMelt(
refreshSession.norevealIndex = norevealIndex;
await ws.db
- .mktx((x) => [x.refreshGroups])
+ .mktx((x) => [x.refreshGroups, x.refreshSessions])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
if (!rg) {
@@ -502,7 +536,7 @@ async function refreshMelt(
if (rg.timestampFinished) {
return;
}
- const rs = rg.refreshSessionPerCoin[coinIndex];
+ const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
if (!rs) {
return;
}
@@ -510,7 +544,7 @@ async function refreshMelt(
return;
}
rs.norevealIndex = norevealIndex;
- await tx.refreshGroups.put(rg);
+ await tx.refreshSessions.put(rs);
});
}
@@ -581,13 +615,16 @@ async function refreshReveal(
`doing refresh reveal for ${refreshGroupId} (old coin ${coinIndex})`,
);
const d = await ws.db
- .mktx((x) => [x.refreshGroups, x.coins, x.denominations])
+ .mktx((x) => [x.refreshGroups, x.refreshSessions, x.coins, x.denominations])
.runReadOnly(async (tx) => {
const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
if (!refreshGroup) {
return;
}
- const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
+ const refreshSession = await tx.refreshSessions.get([
+ refreshGroupId,
+ coinIndex,
+ ]);
if (!refreshSession) {
return;
}
@@ -755,6 +792,7 @@ async function refreshReveal(
x.denominations,
x.coinAvailability,
x.refreshGroups,
+ x.refreshSessions,
])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -762,7 +800,7 @@ async function refreshReveal(
logger.warn("no refresh session found");
return;
}
- const rs = rg.refreshSessionPerCoin[coinIndex];
+ const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
if (!rs) {
return;
}
@@ -858,10 +896,15 @@ async function processRefreshSession(
logger.trace(
`processing refresh session for coin ${coinIndex} of group ${refreshGroupId}`,
);
- let refreshGroup = await ws.db
- .mktx((x) => [x.refreshGroups])
+ let { refreshGroup, refreshSession } = await ws.db
+ .mktx((x) => [x.refreshGroups, x.refreshSessions])
.runReadOnly(async (tx) => {
- return tx.refreshGroups.get(refreshGroupId);
+ const rg = await tx.refreshGroups.get(refreshGroupId);
+ const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]);
+ return {
+ refreshGroup: rg,
+ refreshSession: rs,
+ };
});
if (!refreshGroup) {
return;
@@ -869,18 +912,9 @@ async function processRefreshSession(
if (refreshGroup.statusPerCoin[coinIndex] === RefreshCoinStatus.Finished) {
return;
}
- if (!refreshGroup.refreshSessionPerCoin[coinIndex]) {
- await refreshCreateSession(ws, refreshGroupId, coinIndex);
- refreshGroup = await ws.db
- .mktx((x) => [x.refreshGroups])
- .runReadOnly(async (tx) => {
- return tx.refreshGroups.get(refreshGroupId);
- });
- if (!refreshGroup) {
- return;
- }
+ if (!refreshSession) {
+ refreshSession = await provideRefreshSession(ws, refreshGroupId, coinIndex);
}
- const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
if (!refreshSession) {
if (refreshGroup.statusPerCoin[coinIndex] !== RefreshCoinStatus.Finished) {
throw Error(
@@ -1058,13 +1092,11 @@ export async function createRefreshGroup(
timestampFinished: undefined,
statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending),
oldCoinPubs: oldCoinPubs.map((x) => x.coinPub),
- lastErrorPerCoin: {},
reasonDetails,
reason,
refreshGroupId,
- refreshSessionPerCoin: oldCoinPubs.map(() => undefined),
inputPerCoin: oldCoinPubs.map((x) => x.amount),
- estimatedOutputPerCoin: estimatedOutputPerCoin.map((x) =>
+ expectedOutputPerCoin: estimatedOutputPerCoin.map((x) =>
Amounts.stringify(x),
),
timestampCreated: TalerPreciseTimestamp.now(),
@@ -1164,11 +1196,7 @@ export async function autoRefresh(
if (AbsoluteTime.isExpired(executeThreshold)) {
refreshCoins.push({
coinPub: coin.coinPub,
- amount: Amounts.stringify({
- value: denom.amountVal,
- fraction: denom.amountFrac,
- currency: denom.currency,
- }),
+ amount: denom.value,
});
} else {
const checkThreshold = getAutoRefreshCheckThreshold(denom);