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.ts122
1 files changed, 79 insertions, 43 deletions
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index fb356f0fc..3c4ef207a 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,
@@ -170,18 +174,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 +201,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 +300,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 +325,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 +346,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 +465,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 +484,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 +531,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 +540,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 +548,7 @@ async function refreshMelt(
return;
}
rs.norevealIndex = norevealIndex;
- await tx.refreshGroups.put(rg);
+ await tx.refreshSessions.put(rs);
});
}
@@ -581,13 +619,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 +796,7 @@ async function refreshReveal(
x.denominations,
x.coinAvailability,
x.refreshGroups,
+ x.refreshSessions,
])
.runReadWrite(async (tx) => {
const rg = await tx.refreshGroups.get(refreshGroupId);
@@ -762,7 +804,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 +900,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 +916,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 +1096,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(),