commit b178f297ebb84eee9675a2007363a636fcc82d29
parent 45ae39c9d2e1d24b7aceb37d4cd511591e2c9b78
Author: Florian Dold <florian@dold.me>
Date: Tue, 3 Feb 2026 16:49:52 +0100
more trace logging, use env variable to choose DB backend
Diffstat:
8 files changed, 100 insertions(+), 55 deletions(-)
diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts
@@ -224,6 +224,11 @@ export interface AccessStats {
readItemsPerStore: Record<string, number>;
}
+function logtrace(m: string, ...args: any[]): void {
+ // Use stderr
+ console.error(m, ...args);
+}
+
/**
* Primitive in-memory backend.
*
@@ -385,7 +390,7 @@ export class MemoryBackend implements Backend {
* Only exports data that has been committed.
*/
exportDump(): MemoryBackendDump {
- this.enableTracing && console.log("exporting dump");
+ this.enableTracing && logtrace("exporting dump");
const dbDumps: { [name: string]: DatabaseDump } = {};
for (const dbName of Object.keys(this.databases)) {
const db = this.databases[dbName];
@@ -413,7 +418,7 @@ export class MemoryBackend implements Backend {
async getDatabases(): Promise<{ name: string; version: number }[]> {
if (this.enableTracing) {
- console.log("TRACING: getDatabase");
+ logtrace("TRACING: getDatabase");
}
const dbList = [];
for (const name in this.databases) {
@@ -427,7 +432,7 @@ export class MemoryBackend implements Backend {
async deleteDatabase(name: string): Promise<void> {
if (this.enableTracing) {
- console.log(`TRACING: deleteDatabase(${name})`);
+ logtrace(`TRACING: deleteDatabase(${name})`);
}
const myDb = this.databases[name];
if (!myDb) {
@@ -447,7 +452,7 @@ export class MemoryBackend implements Backend {
async connectDatabase(name: string): Promise<ConnectResult> {
if (this.enableTracing) {
- console.log(`TRACING: connectDatabase(${name})`);
+ logtrace(`TRACING: connectDatabase(${name})`);
}
const connectionId = this.connectionIdCounter++;
const connectionCookie = `connection-${connectionId}`;
@@ -498,7 +503,7 @@ export class MemoryBackend implements Backend {
): Promise<DatabaseTransaction> {
const transactionCookie = `tx-${this.transactionIdCounter++}`;
if (this.enableTracing) {
- console.log(`TRACING: beginTransaction ${transactionCookie}`);
+ logtrace(`TRACING: beginTransaction ${transactionCookie}`);
}
const myConn = this.connections[conn.connectionCookie];
if (!myConn) {
@@ -511,7 +516,7 @@ export class MemoryBackend implements Backend {
while (myDb.txLevel !== TransactionLevel.None) {
if (this.enableTracing) {
- console.log(`TRACING: beginTransaction -- waiting for others to close`);
+ logtrace(`TRACING: beginTransaction -- waiting for others to close`);
}
await this.transactionDoneCond.wait();
}
@@ -544,7 +549,7 @@ export class MemoryBackend implements Backend {
newVersion: number,
): Promise<DatabaseTransaction> {
if (this.enableTracing) {
- console.log(`TRACING: enterVersionChange`);
+ logtrace(`TRACING: enterVersionChange`);
}
const transactionCookie = `tx-vc-${this.transactionIdCounter++}`;
const myConn = this.connections[conn.connectionCookie];
@@ -574,7 +579,7 @@ export class MemoryBackend implements Backend {
async close(conn: DatabaseConnection): Promise<void> {
if (this.enableTracing) {
- console.log(`TRACING: close (${conn.connectionCookie})`);
+ logtrace(`TRACING: close (${conn.connectionCookie})`);
}
const myConn = this.connections[conn.connectionCookie];
if (!myConn) {
@@ -608,7 +613,7 @@ export class MemoryBackend implements Backend {
newName: string,
): void {
if (this.enableTracing) {
- console.log(`TRACING: renameIndex(?, ${oldName}, ${newName})`);
+ logtrace(`TRACING: renameIndex(?, ${oldName}, ${newName})`);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -647,7 +652,7 @@ export class MemoryBackend implements Backend {
indexName: string,
): void {
if (this.enableTracing) {
- console.log(`TRACING: deleteIndex(${indexName})`);
+ logtrace(`TRACING: deleteIndex(${indexName})`);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -676,7 +681,7 @@ export class MemoryBackend implements Backend {
deleteObjectStore(btx: DatabaseTransaction, name: string): void {
if (this.enableTracing) {
- console.log(
+ logtrace(
`TRACING: deleteObjectStore(${name}) in ${btx.transactionCookie}`,
);
}
@@ -716,7 +721,7 @@ export class MemoryBackend implements Backend {
newName: string,
): void {
if (this.enableTracing) {
- console.log(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
+ logtrace(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
}
const myConn = this.requireConnectionFromTransaction(btx);
@@ -755,9 +760,7 @@ export class MemoryBackend implements Backend {
autoIncrement: boolean,
): void {
if (this.enableTracing) {
- console.log(
- `TRACING: createObjectStore(${btx.transactionCookie}, ${name})`,
- );
+ logtrace(`TRACING: createObjectStore(${btx.transactionCookie}, ${name})`);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -799,7 +802,7 @@ export class MemoryBackend implements Backend {
unique: boolean,
): void {
if (this.enableTracing) {
- console.log(`TRACING: createIndex(${indexName})`);
+ logtrace(`TRACING: createIndex(${indexName})`);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -899,7 +902,7 @@ export class MemoryBackend implements Backend {
range: IDBKeyRange,
): Promise<void> {
if (this.enableTracing) {
- console.log(`TRACING: deleteRecord from store ${objectStoreName}`);
+ logtrace(`TRACING: deleteRecord from store ${objectStoreName}`);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -994,7 +997,7 @@ export class MemoryBackend implements Backend {
throw Error("index referenced by object store does not exist");
}
this.enableTracing &&
- console.log(
+ logtrace(
`deleting from index ${indexName} for object store ${objectStoreName}`,
);
const indexProperties =
@@ -1022,9 +1025,7 @@ export class MemoryBackend implements Backend {
indexProperties: IndexProperties,
): void {
if (this.enableTracing) {
- console.log(
- `deleteFromIndex(${index.modifiedName || index.originalName})`,
- );
+ logtrace(`deleteFromIndex(${index.modifiedName || index.originalName})`);
}
if (value === undefined || value === null) {
throw Error("cannot delete null/undefined value from index");
@@ -1059,8 +1060,8 @@ export class MemoryBackend implements Backend {
req: ObjectStoreGetQuery,
): Promise<RecordGetResponse> {
if (this.enableTracing) {
- console.log(`TRACING: getObjectStoreRecords`);
- console.log("query", req);
+ logtrace(`TRACING: getObjectStoreRecords`);
+ logtrace("query", req);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -1128,7 +1129,7 @@ export class MemoryBackend implements Backend {
(this.accessStats.readItemsPerStore[k] ?? 0) + resp.count;
}
if (this.enableTracing) {
- console.log(`TRACING: getRecords got ${resp.count} results`);
+ logtrace(`TRACING: getRecords got ${resp.count} results`);
}
return resp;
}
@@ -1138,8 +1139,8 @@ export class MemoryBackend implements Backend {
req: IndexGetQuery,
): Promise<RecordGetResponse> {
if (this.enableTracing) {
- console.log(`TRACING: getIndexRecords`);
- console.log("query", req);
+ logtrace(`TRACING: getIndexRecords`);
+ logtrace("query", req);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
@@ -1216,7 +1217,7 @@ export class MemoryBackend implements Backend {
(this.accessStats.readItemsPerIndex[k] ?? 0) + resp.count;
}
if (this.enableTracing) {
- console.log(`TRACING: getIndexRecords got ${resp.count} results`);
+ logtrace(`TRACING: getIndexRecords got ${resp.count} results`);
}
return resp;
}
@@ -1226,8 +1227,8 @@ export class MemoryBackend implements Backend {
storeReq: RecordStoreRequest,
): Promise<RecordStoreResponse> {
if (this.enableTracing) {
- console.log(`TRACING: storeRecord`);
- console.log(
+ logtrace(`TRACING: storeRecord`);
+ logtrace(
`key ${storeReq.key}, record ${JSON.stringify(
structuredEncapsulate(storeReq.value),
)}`,
@@ -1397,9 +1398,7 @@ export class MemoryBackend implements Backend {
indexProperties: IndexProperties,
): void {
if (this.enableTracing) {
- console.log(
- `insertIntoIndex(${index.modifiedName || index.originalName})`,
- );
+ logtrace(`insertIntoIndex(${index.modifiedName || index.originalName})`);
}
let indexData = index.modifiedData || index.originalData;
let indexKeys;
@@ -1451,7 +1450,7 @@ export class MemoryBackend implements Backend {
async rollback(btx: DatabaseTransaction): Promise<void> {
if (this.enableTracing) {
- console.log(`TRACING: rollback`);
+ logtrace(`TRACING: rollback`);
}
const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
@@ -1491,7 +1490,7 @@ export class MemoryBackend implements Backend {
async commit(btx: DatabaseTransaction): Promise<void> {
if (this.enableTracing) {
- console.log(`TRACING: commit`);
+ logtrace(`TRACING: commit`);
}
const myConn = this.requireConnectionFromTransaction(btx);
const db = this.databases[myConn.dbName];
diff --git a/packages/idb-bridge/src/testingdb.ts b/packages/idb-bridge/src/testingdb.ts
@@ -13,7 +13,9 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { MemoryBackend } from "./MemoryBackend.js";
import { createSqliteBackend } from "./SqliteBackend.js";
+import { Backend } from "./backend-interface.js";
import { BridgeIDBFactory } from "./bridge-idb.js";
import { IDBFactory } from "./idbtypes.js";
import { createNodeHelperSqlite3Impl } from "./node-helper-sqlite3-impl.js";
@@ -21,21 +23,25 @@ import { createNodeHelperSqlite3Impl } from "./node-helper-sqlite3-impl.js";
let idbFactory: IDBFactory | undefined = undefined;
export async function initTestIndexedDB(): Promise<void> {
- // const backend = new MemoryBackend();
- // backend.enableTracing = true;
-
- const sqlite3Impl = await createNodeHelperSqlite3Impl({
- enableTracing: false,
- });
-
- // const sqlite3Impl = await createNodeBetterSqlite3Impl();
-
- const backend = await createSqliteBackend(sqlite3Impl, {
- filename: ":memory:",
- });
+ let backend: Backend;
+ if (process.env.IDB_TEST_BACKEND == "memory") {
+ const memBackend = new MemoryBackend();
+ backend = memBackend;
+ // backend.enableTracing = true;
+ memBackend.enableTracing = false;
+ } else {
+ const sqlite3Impl = await createNodeHelperSqlite3Impl({
+ enableTracing: false,
+ });
+
+ // const sqlite3Impl = await createNodeBetterSqlite3Impl();
+
+ backend = await createSqliteBackend(sqlite3Impl, {
+ filename: ":memory:",
+ });
+ }
idbFactory = new BridgeIDBFactory(backend);
- backend.enableTracing = false;
BridgeIDBFactory.enableTracing = false;
}
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
@@ -2749,6 +2749,7 @@ export class WalletCli {
self.dbfile
}' api '${op}' ${shellWrap(JSON.stringify(payload))}`;
const resp = await sh(self.globalTestState, logName, command);
+ logger.trace(`command: ${j2s(command)}`);
logger.info("--- wallet core response ---");
logger.info(resp);
logger.info("--- end of response ---");
diff --git a/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts b/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts
@@ -22,6 +22,7 @@ import {
encodeCrock,
getRandomBytes,
j2s,
+ Logger,
TalerError,
} from "@gnu-taler/taler-util";
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
@@ -40,6 +41,8 @@ import {
import { createSimpleTestkudosEnvironmentV2 } from "../harness/environments.js";
import { GlobalTestState } from "../harness/harness.js";
+const logger = new Logger("test-exchange-deposit.ts");
+
/**
* Run test for basic, bank-integrated withdrawal and payment.
*/
@@ -85,6 +88,8 @@ export async function runExchangeDepositTest(t: GlobalTestState) {
{},
);
+ logger.info(`found denomination: ${j2s(d1)}`);
+
const coin = await withdrawCoin({
http,
cryptoApi,
diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts
@@ -59,7 +59,6 @@ import {
readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
-import { DenominationRecord } from "./db.js";
import { ExchangeInfo, downloadExchangeInfo } from "./exchanges.js";
import { getBankStatusUrl, getBankWithdrawalInfo } from "./withdraw.js";
@@ -191,6 +190,10 @@ export async function withdrawCoin(args: {
export interface FindDenomOptions {}
+/**
+ * Find a denomination offered by the exchange
+ * that matches exactly the requested value.
+ */
export function findDenomOrThrow(
exchangeInfo: ExchangeInfo,
amount: AmountString,
@@ -206,6 +209,9 @@ export function findDenomOrThrow(
}
case "RSA":
case "RSA+age_restricted": {
+ if (Amounts.cmp(denomFamily.value, amount) != 0) {
+ continue;
+ }
if (denomFamily.cipher === "RSA+age_restricted") {
ageMask = denomFamily.age_mask;
}
diff --git a/packages/taler-wallet-core/src/denomSelection.ts b/packages/taler-wallet-core/src/denomSelection.ts
@@ -45,7 +45,8 @@ const logger = new Logger("denomSelection.ts");
*
* @param amountAvailable available withdrawal amount
* @param denoms list of denominations that can be used,
- * must be validated to be withdrawable
+ * must be validated to be withdrawable and sorted
+ * by descending denom value
* @param opts extra options
*/
export function selectWithdrawalDenominations(
@@ -65,16 +66,22 @@ export function selectWithdrawalDenominations(
let earliestDepositExpiration: AbsoluteTime | undefined;
let hasDenomWithAgeRestriction = false;
- for (const d of denoms) {
+ // Precondition check
+ for (let i = 0; i < denoms.length; i++) {
+ const d = denoms[i];
if (!isWithdrawableDenom(d)) {
throw Error(
"non-withdrawable denom passed to selectWithdrawalDenominations",
);
}
+ if (
+ i < denoms.length - 1 &&
+ Amounts.cmp(denoms[i].value, denoms[i + 1].value) < 0
+ ) {
+ throw Error("denoms must be sorted by descending value");
+ }
}
- denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
-
if (logger.shouldLogTrace()) {
logger.trace(
`selecting withdrawal denoms for ${Amounts.stringify(amountAvailable)}`,
diff --git a/packages/taler-wallet-core/src/host-impl.node.ts b/packages/taler-wallet-core/src/host-impl.node.ts
@@ -52,7 +52,7 @@ async function makeFileDb(
args: DefaultNodeWalletArgs = {},
): Promise<WalletDatabaseImplementation> {
const myBackend = new MemoryBackend();
- myBackend.enableTracing = false;
+ myBackend.enableTracing = true;
const storagePath = args.persistentStoragePath;
if (storagePath) {
try {
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -1388,7 +1388,7 @@ async function getWithdrawableDenoms(
* isn't validated yet.
*/
export async function getWithdrawableDenomsTx(
- wex: WalletExecutionContext,
+ _wex: WalletExecutionContext,
tx: WalletDbReadOnlyTransaction<["denominations", "denominationFamilies"]>,
exchangeBaseUrl: string,
currency: string,
@@ -1399,6 +1399,13 @@ export async function getWithdrawableDenomsTx(
await tx.denominationFamilies.indexes.byExchangeBaseUrl.getAll(
exchangeBaseUrl,
);
+ if (logger.shouldLogTrace()) {
+ const maxStr = maxAmount ? Amounts.stringify(maxAmount) : "<unknown>";
+ logger.trace(
+ `getting withdrawable denoms for ${currency} / ${maxStr} at ${exchangeBaseUrl}`,
+ );
+ logger.trace(`have ${allFamilies.length} families`);
+ }
const relevantDenoms: DenominationRecord[] = [];
for (const fam of allFamilies) {
const famCurrency = Amounts.currencyOf(fam.familyParams.value);
@@ -1415,6 +1422,9 @@ export async function getWithdrawableDenomsTx(
// Denom value too high, no point in selecting.
continue;
}
+ if (logger.shouldLogTrace()) {
+ logger.trace(`finding representative for denom family: ${j2s(fam)}`);
+ }
const fpSerial = fam.denominationFamilySerial;
checkDbInvariant(typeof fpSerial === "number", "denominationFamilySerial");
// Now we need to find a representative denom for the family.
@@ -1433,15 +1443,18 @@ export async function getWithdrawableDenomsTx(
dr0.value.stampExpireWithdraw >= dbNow)
)
) {
+ logger.trace(`continuing cursor past ${j2s([fpSerial, dbNow])}`);
denomCursor.continue([fpSerial, dbNow]);
}
while (1) {
const dr = await denomCursor.current();
if (!dr.hasValue) {
+ logger.trace(`cursor exhausted`);
break;
}
if (dr.value.denominationFamilySerial != fpSerial) {
// Cursor went to next serial already, we need to stop.
+ logger.trace(`cursor past target`);
break;
}
if (isCandidateWithdrawableDenomRec(dr.value)) {
@@ -1449,12 +1462,20 @@ export async function getWithdrawableDenomsTx(
break;
}
+ logger.trace(`continuing cursor to next record`);
+
denomCursor.continue();
}
if (denom) {
relevantDenoms.push(denom);
+ } else {
+ logger.warn(`no representative denom for family with serial=${fpSerial}`);
}
}
+ relevantDenoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
+ if (logger.shouldLogTrace()) {
+ logger.trace(`relevant denoms: ${j2s(relevantDenoms)}`);
+ }
return relevantDenoms;
}
@@ -2280,7 +2301,7 @@ async function getWithdrawalCandidateDenoms(
): Promise<DenominationRecord[]> {
await updateWithdrawalDenomsForExchange(wex, exchangeBaseUrl);
return await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
- return getWithdrawableDenomsTx(
+ return await getWithdrawableDenomsTx(
wex,
tx,
exchangeBaseUrl,