[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-typescript-core] branch master updated (88dd48fb8 -> e63dfd6bb)
From: |
Admin |
Subject: |
[taler-typescript-core] branch master updated (88dd48fb8 -> e63dfd6bb) |
Date: |
Mon, 17 Feb 2025 20:47:16 +0100 |
This is an automated email from the git hooks/post-receive script.
dold pushed a change to branch master
in repository taler-typescript-core.
from 88dd48fb8 fix #9539
new f98e841ee wallet-core: improve DB transaction cancellation error
reporting
new e63dfd6bb wallet cli: implement wait flag for testing withdrawal
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
packages/taler-wallet-cli/src/index.ts | 55 ++++-
packages/taler-wallet-core/src/balance.ts | 2 +-
packages/taler-wallet-core/src/query.ts | 256 +++++++++------------
packages/taler-wallet-core/src/testing.ts | 3 +-
packages/taler-wallet-core/src/wallet-api-types.ts | 5 +-
packages/taler-wallet-core/src/wallet.ts | 10 +-
6 files changed, 167 insertions(+), 164 deletions(-)
diff --git a/packages/taler-wallet-cli/src/index.ts
b/packages/taler-wallet-cli/src/index.ts
index e306f3225..c3eb74a1c 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -43,6 +43,7 @@ import {
summarizeTalerErrorDetail,
TalerUriAction,
TransactionIdStr,
+ TransactionMajorState,
WalletNotification,
} from "@gnu-taler/taler-util";
import { clk } from "@gnu-taler/taler-util/clk";
@@ -57,7 +58,6 @@ import {
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
import { JsonMessage, runRpcServer } from "@gnu-taler/taler-util/twrpc";
import {
- AccessStats,
createNativeWalletHost2,
nativeCrypto,
Wallet,
@@ -378,7 +378,10 @@ async function withWallet<T>(
await wh.wallet.client.call(WalletApiOperation.Shutdown, {});
if (process.env.TALER_WALLET_DBSTATS) {
console.log("database stats:");
- const stats = await
wh.wallet.client.call(WalletApiOperation.TestingGetDbStats, {});
+ const stats = await wh.wallet.client.call(
+ WalletApiOperation.TestingGetDbStats,
+ {},
+ );
console.log(j2s(stats));
}
return result;
@@ -1750,21 +1753,53 @@ const testCli = walletCli.subcommand("testingArgs",
"testing", {
testCli
.subcommand("withdrawTestkudos", "withdraw-testkudos")
+ .flag("wait", ["--wait"])
.action(async (args) => {
await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- await wallet.client.call(WalletApiOperation.WithdrawTestkudos, {});
+ const resp = await wallet.client.call(
+ WalletApiOperation.WithdrawTestkudos,
+ {},
+ );
+ if (args.withdrawTestkudos.wait) {
+ await wallet.client.call(
+ WalletApiOperation.TestingWaitTransactionState,
+ {
+ transactionId: resp.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ },
+ );
+ }
});
});
-testCli.subcommand("withdrawKudos", "withdraw-kudos").action(async (args) => {
- await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- await wallet.client.call(WalletApiOperation.WithdrawTestBalance, {
- amount: "KUDOS:50" as AmountString,
- corebankApiBaseUrl: "https://bank.demo.taler.net/",
- exchangeBaseUrl: "https://exchange.demo.taler.net/",
+testCli
+ .subcommand("withdrawKudos", "withdraw-kudos")
+ .flag("wait", ["--wait"])
+ .action(async (args) => {
+ await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
+ const resp = await wallet.client.call(
+ WalletApiOperation.WithdrawTestBalance,
+ {
+ amount: "KUDOS:50" as AmountString,
+ corebankApiBaseUrl: "https://bank.demo.taler.net/",
+ exchangeBaseUrl: "https://exchange.demo.taler.net/",
+ },
+ );
+ if (args.withdrawKudos.wait) {
+ await wallet.client.call(
+ WalletApiOperation.TestingWaitTransactionState,
+ {
+ transactionId: resp.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ },
+ );
+ }
});
});
-});
class PerfTimer {
tStarted: bigint | undefined;
diff --git a/packages/taler-wallet-core/src/balance.ts
b/packages/taler-wallet-core/src/balance.ts
index b4cb02481..6bcdb3ada 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -720,7 +720,7 @@ export async function getPaymentBalanceDetailsInTx(
balanceExchangeDepositable: Amounts.zeroOfCurrency(req.currency),
};
- logger.info(`computing balance details for ${j2s(req)}`);
+ logger.trace(`computing balance details for ${j2s(req)}`);
const availableCoins = await tx.coinAvailability.getAll();
diff --git a/packages/taler-wallet-core/src/query.ts
b/packages/taler-wallet-core/src/query.ts
index ab8ca7bc8..78260b4e7 100644
--- a/packages/taler-wallet-core/src/query.ts
+++ b/packages/taler-wallet-core/src/query.ts
@@ -85,7 +85,10 @@ const logExtra = false;
let idbRequestPromId = 1;
-function requestToPromise(req: IDBRequest): Promise<any> {
+function requestToPromise(
+ req: IDBRequest,
+ internalContext: InternalTransactionContext,
+): Promise<any> {
const myId = idbRequestPromId++;
if (logExtra) {
logger.trace(`started db request ${myId}`);
@@ -102,6 +105,14 @@ function requestToPromise(req: IDBRequest): Promise<any> {
if (logExtra) {
logger.trace(`finished db request ${myId} with error`);
}
+ if (internalContext.isAborted) {
+ reject(
+ new TransactionAbortedError(
+ internalContext.abortExn?.message ?? "Aborted",
+ ),
+ );
+ return;
+ }
if (
req.error != null &&
"name" in req.error &&
@@ -594,15 +605,23 @@ function runTx<Arg, Res>(
tx: IDBTransaction,
arg: Arg,
f: (t: Arg, t2: IDBTransaction) => Promise<Res>,
- triggerContext: InternalTriggerContext,
- cancellationToken: CancellationToken,
+ internalContext: InternalTransactionContext,
): Promise<Res> {
// Create stack trace in case we need to to print later where
// the transaction was started.
const stack = Error("Failed transaction was started here.");
+ const cancellationToken = internalContext.cancellationToken;
+
const unregisterOnCancelled = cancellationToken.onCancelled(() => {
logger.trace("aborting transaction due to cancellation");
+ if (!internalContext.isAborted) {
+ internalContext.isAborted = true;
+ const abortExn = new CancellationToken.CancellationError(
+ cancellationToken.reason,
+ );
+ internalContext.abortExn = abortExn;
+ }
tx.abort();
});
@@ -612,7 +631,6 @@ function runTx<Arg, Res>(
let funResult: any = undefined;
let gotFunResult = false;
let transactionException: any = undefined;
- let aborted = false;
tx.oncomplete = () => {
logger.trace("transaction completed");
// This is a fatal error: The transaction completed *before*
@@ -630,7 +648,7 @@ function runTx<Arg, Res>(
} else {
resolve(funResult);
}
- triggerContext.handleAfterCommit();
+ internalContext.handleAfterCommit();
unregisterOnCancelled();
};
tx.onerror = () => {
@@ -653,9 +671,10 @@ function runTx<Arg, Res>(
tx.onabort = () => {
logger.trace("transaction was aborted");
if (cancellationToken.isCancelled) {
- reject(
- new CancellationToken.CancellationError(cancellationToken.reason),
+ const abortExn = new CancellationToken.CancellationError(
+ cancellationToken.reason,
);
+ reject(abortExn);
return;
}
let msg: string;
@@ -666,11 +685,13 @@ function runTx<Arg, Res>(
} else {
msg = "Transaction aborted (no DB error)";
}
- aborted = true;
+ const abortExn = new TransactionAbortedError(msg);
+ internalContext.isAborted = true;
+ internalContext.abortExn = abortExn;
unregisterOnCancelled();
logger.error(msg);
logger.error(`${stack.stack ?? stack}`);
- reject(new TransactionAbortedError(msg));
+ reject(abortExn);
};
const resP = Promise.resolve().then(() => f(arg, tx));
resP
@@ -702,83 +723,14 @@ function runTx<Arg, Res>(
});
}
-function makeReadContext(
- tx: IDBTransaction,
- storePick: { [n: string]: StoreWithIndexes<any, any, any> },
- triggerContext: InternalTriggerContext,
-): any {
- const ctx: { [s: string]: StoreReadOnlyAccessor<any, any> } = {};
- for (const storeAlias in storePick) {
- const indexes: { [s: string]: IndexReadOnlyAccessor<any> } = {};
- const swi = storePick[storeAlias];
- const storeName = swi.storeName;
- for (const indexAlias in storePick[storeAlias].indexMap) {
- const indexDescriptor: IndexDescriptor =
- storePick[storeAlias].indexMap[indexAlias];
- const indexName = indexDescriptor.name;
- indexes[indexAlias] = {
- get(key) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx.objectStore(storeName).index(indexName).get(key);
- return requestToPromise(req);
- },
- iter(query) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx
- .objectStore(storeName)
- .index(indexName)
- .openCursor(query);
- return new ResultStream<any>(req);
- },
- getAll(query, count) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx
- .objectStore(storeName)
- .index(indexName)
- .getAll(query, count);
- return requestToPromise(req);
- },
- getAllKeys(query, count) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx
- .objectStore(storeName)
- .index(indexName)
- .getAllKeys(query, count);
- return requestToPromise(req);
- },
- count(query) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx.objectStore(storeName).index(indexName).count(query);
- return requestToPromise(req);
- },
- };
- }
- ctx[storeAlias] = {
- indexes,
- get(key) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx.objectStore(storeName).get(key);
- return requestToPromise(req);
- },
- getAll(query, count) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx.objectStore(storeName).getAll(query, count);
- return requestToPromise(req);
- },
- iter(query) {
- triggerContext.storesAccessed.add(storeName);
- const req = tx.objectStore(storeName).openCursor(query);
- return new ResultStream<any>(req);
- },
- };
- }
- return ctx;
-}
-
-function makeWriteContext(
+/**
+ * Create a transaction handle that will be passed
+ * to the main handler for the transaction.
+ */
+function makeTxContext(
tx: IDBTransaction,
storePick: { [n: string]: StoreWithIndexes<any, any, any> },
- triggerContext: InternalTriggerContext,
+ internalContext: InternalTransactionContext,
): any {
const ctx: { [s: string]: StoreReadWriteAccessor<any, any> } = {};
for (const storeAlias in storePick) {
@@ -791,12 +743,14 @@ function makeWriteContext(
const indexName = indexDescriptor.name;
indexes[indexAlias] = {
get(key) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx.objectStore(storeName).index(indexName).get(key);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
iter(query) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
@@ -804,68 +758,86 @@ function makeWriteContext(
return new ResultStream<any>(req);
},
getAll(query, count) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
.getAll(query, count);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
getAllKeys(query, count) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
.getAllKeys(query, count);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
count(query) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx.objectStore(storeName).index(indexName).count(query);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
};
}
ctx[storeAlias] = {
indexes,
get(key) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx.objectStore(storeName).get(key);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
getAll(query, count) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx.objectStore(storeName).getAll(query, count);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
iter(query) {
- triggerContext.storesAccessed.add(storeName);
+ internalContext.throwIfInactive();
+ internalContext.storesAccessed.add(storeName);
const req = tx.objectStore(storeName).openCursor(query);
return new ResultStream<any>(req);
},
async add(r, k) {
- triggerContext.storesAccessed.add(storeName);
- triggerContext.storesModified.add(storeName);
+ internalContext.throwIfInactive();
+ if (!internalContext.allowWrite) {
+ throw Error("attempting write in a read-only transaction");
+ }
+ internalContext.storesAccessed.add(storeName);
+ internalContext.storesModified.add(storeName);
const req = tx.objectStore(storeName).add(r, k);
- const key = await requestToPromise(req);
+ const key = await requestToPromise(req, internalContext);
return {
key: key,
};
},
async put(r, k) {
- triggerContext.storesAccessed.add(storeName);
- triggerContext.storesModified.add(storeName);
+ internalContext.throwIfInactive();
+ if (!internalContext.allowWrite) {
+ throw Error("attempting write in a read-only transaction");
+ }
+ internalContext.storesAccessed.add(storeName);
+ internalContext.storesModified.add(storeName);
const req = tx.objectStore(storeName).put(r, k);
- const key = await requestToPromise(req);
+ const key = await requestToPromise(req, internalContext);
return {
key: key,
};
},
delete(k) {
- triggerContext.storesAccessed.add(storeName);
- triggerContext.storesModified.add(storeName);
+ internalContext.throwIfInactive();
+ if (!internalContext.allowWrite) {
+ throw Error("attempting write in a read-only transaction");
+ }
+ internalContext.storesAccessed.add(storeName);
+ internalContext.storesModified.add(storeName);
const req = tx.objectStore(storeName).delete(k);
- return requestToPromise(req);
+ return requestToPromise(req, internalContext);
},
};
}
@@ -969,17 +941,26 @@ export interface TriggerSpec {
// beforeCommit<State>? (tx: Transaction, s: State | undefined) =>
Promise<void>;
}
-class InternalTriggerContext {
+/**
+ * Additional state we store for every IndexedDB transaction opened
+ * via the query helper.
+ */
+class InternalTransactionContext {
+ isAborted = false;
storesScope: Set<string>;
storesAccessed: Set<string> = new Set();
storesModified: Set<string> = new Set();
+ allowWrite: boolean;
+ abortExn: TransactionAbortedError | undefined;
constructor(
private triggerSpec: TriggerSpec,
private mode: IDBTransactionMode,
scope: string[],
+ public cancellationToken: CancellationToken,
) {
this.storesScope = new Set(scope);
+ this.allowWrite = mode === "readwrite" || mode === "versionchange";
}
handleAfterCommit() {
@@ -992,6 +973,13 @@ class InternalTriggerContext {
});
}
}
+
+ throwIfInactive() {
+ this.cancellationToken.throwIfCancelled();
+ if (this.isAborted) {
+ throw this.abortExn;
+ }
+ }
}
/**
@@ -1029,20 +1017,15 @@ export class DbAccessImpl<StoreMap> implements
DbAccess<StoreMap> {
accessibleStores[swi.storeName] = swi;
}
const mode = "readwrite";
- const triggerContext = new InternalTriggerContext(
+ const triggerContext = new InternalTransactionContext(
this.triggers,
mode,
strStoreNames,
- );
- const tx = this.db.transaction(strStoreNames, mode);
- const writeContext = makeWriteContext(tx, accessibleStores,
triggerContext);
- return await runTx(
- tx,
- writeContext,
- txf,
- triggerContext,
this.cancellationToken,
);
+ const tx = this.db.transaction(strStoreNames, mode);
+ const writeContext = makeTxContext(tx, accessibleStores, triggerContext);
+ return await runTx(tx, writeContext, txf, triggerContext);
}
async runAllStoresReadOnlyTx<T>(
@@ -1063,20 +1046,15 @@ export class DbAccessImpl<StoreMap> implements
DbAccess<StoreMap> {
accessibleStores[swi.storeName] = swi;
}
const mode = "readonly";
- const triggerContext = new InternalTriggerContext(
+ const triggerContext = new InternalTransactionContext(
this.triggers,
mode,
strStoreNames,
- );
- const tx = this.db.transaction(strStoreNames, mode);
- const writeContext = makeReadContext(tx, accessibleStores, triggerContext);
- const res = await runTx(
- tx,
- writeContext,
- txf,
- triggerContext,
this.cancellationToken,
);
+ const tx = this.db.transaction(strStoreNames, mode);
+ const writeContext = makeTxContext(tx, accessibleStores, triggerContext);
+ const res = await runTx(tx, writeContext, txf, triggerContext);
return res;
}
@@ -1096,20 +1074,15 @@ export class DbAccessImpl<StoreMap> implements
DbAccess<StoreMap> {
accessibleStores[swi.storeName] = swi;
}
const mode = "readwrite";
- const triggerContext = new InternalTriggerContext(
+ const triggerContext = new InternalTransactionContext(
this.triggers,
mode,
strStoreNames,
- );
- const tx = this.db.transaction(strStoreNames, mode);
- const writeContext = makeWriteContext(tx, accessibleStores,
triggerContext);
- const res = await runTx(
- tx,
- writeContext,
- txf,
- triggerContext,
this.cancellationToken,
);
+ const tx = this.db.transaction(strStoreNames, mode);
+ const writeContext = makeTxContext(tx, accessibleStores, triggerContext);
+ const res = await runTx(tx, writeContext, txf, triggerContext);
return res;
}
@@ -1129,20 +1102,15 @@ export class DbAccessImpl<StoreMap> implements
DbAccess<StoreMap> {
accessibleStores[swi.storeName] = swi;
}
const mode = "readonly";
- const triggerContext = new InternalTriggerContext(
+ const triggerContext = new InternalTransactionContext(
this.triggers,
mode,
strStoreNames,
- );
- const tx = this.db.transaction(strStoreNames, mode);
- const readContext = makeReadContext(tx, accessibleStores, triggerContext);
- const res = await runTx(
- tx,
- readContext,
- txf,
- triggerContext,
this.cancellationToken,
);
+ const tx = this.db.transaction(strStoreNames, mode);
+ const readContext = makeTxContext(tx, accessibleStores, triggerContext);
+ const res = await runTx(tx, readContext, txf, triggerContext);
return res;
}
}
diff --git a/packages/taler-wallet-core/src/testing.ts
b/packages/taler-wallet-core/src/testing.ts
index eeb439715..7abdabf6b 100644
--- a/packages/taler-wallet-core/src/testing.ts
+++ b/packages/taler-wallet-core/src/testing.ts
@@ -45,6 +45,7 @@ import {
TalerCorebankApiClient,
TestPayArgs,
TestPayResult,
+ TransactionIdStr,
TransactionMajorState,
TransactionMinorState,
TransactionState,
@@ -100,7 +101,7 @@ export interface WithdrawTestBalanceResult {
/**
* Transaction ID of the newly created withdrawal transaction.
*/
- transactionId: string;
+ transactionId: TransactionIdStr;
/**
* Account of the user registered for the withdrawal.
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts
b/packages/taler-wallet-core/src/wallet-api-types.ts
index 3fb806387..254f0eafa 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -178,6 +178,7 @@ import {
RunBackupCycleRequest,
} from "./backup/index.js";
import { PaymentBalanceDetails } from "./balance.js";
+import { WithdrawTestBalanceResult } from "./testing.js";
export enum WalletApiOperation {
InitWallet = "initWallet",
@@ -1170,7 +1171,7 @@ export type TestCryptoOp = {
export type WithdrawTestBalanceOp = {
op: WalletApiOperation.WithdrawTestBalance;
request: WithdrawTestBalanceRequest;
- response: EmptyObject;
+ response: WithdrawTestBalanceResult;
};
/**
@@ -1179,7 +1180,7 @@ export type WithdrawTestBalanceOp = {
export type WithdrawTestkudosOp = {
op: WalletApiOperation.WithdrawTestkudos;
request: EmptyObject;
- response: EmptyObject;
+ response: WithdrawTestBalanceResult;
};
/**
diff --git a/packages/taler-wallet-core/src/wallet.ts
b/packages/taler-wallet-core/src/wallet.ts
index 96a117dab..c32f7163c 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -344,6 +344,7 @@ import {
getActiveTaskIds,
} from "./shepherd.js";
import {
+ WithdrawTestBalanceResult,
runIntegrationTest,
runIntegrationTest2,
testPay,
@@ -884,21 +885,18 @@ async function handleSetWalletRunConfig(
}
async function handleWithdrawTestkudos(wex: WalletExecutionContext) {
- await withdrawTestBalance(wex, {
+ return await withdrawTestBalance(wex, {
amount: "TESTKUDOS:10" as AmountString,
corebankApiBaseUrl: "https://bank.test.taler.net/",
exchangeBaseUrl: "https://exchange.test.taler.net/",
});
- // FIXME: Is this correct?
- return {};
}
async function handleWithdrawTestBalance(
wex: WalletExecutionContext,
req: WithdrawTestBalanceRequest,
-): Promise<EmptyObject> {
- await withdrawTestBalance(wex, req);
- return {};
+): Promise<WithdrawTestBalanceResult> {
+ return await withdrawTestBalance(wex, req);
}
async function handleRunIntegrationTest(
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-typescript-core] branch master updated (88dd48fb8 -> e63dfd6bb),
Admin <=