Skip to content

Commit

Permalink
Merge pull request #2686 from dusk-network/feature-2680
Browse files Browse the repository at this point in the history
web-wallet: Fix data returned by IndexedDB
  • Loading branch information
ascartabelli authored Oct 17, 2024
2 parents 6051728 + 3cf7571 commit 9675f47
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 24 deletions.
8 changes: 4 additions & 4 deletions web-wallet/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"fake-indexeddb": "6.0.0",
"jsdom": "24.1.0",
"jsdom-worker": "0.3.0",
"lamb-types": "0.61.9",
"lamb-types": "0.61.11",
"postcss-nested": "6.0.1",
"prettier": "3.3.2",
"prettier-plugin-svelte": "3.2.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it } from "vitest";
import { sortWith } from "lamb";

import {
cachePendingNotesInfo,
Expand All @@ -9,15 +10,41 @@ import {

import { fillCacheDatabase, getCacheDatabase, sortByNullifier } from "..";

/**
* We need to sort the entries in tests as the
* database doesn't guarantee a sort order.
*
* @typedef {{ nullifier: ArrayBuffer }} T
* @type {<U extends T>(entries: U[]) => U[]}
*/
const sortByDbNullifier = sortWith([
/** @type {(entry: T) => string} */ (
({ nullifier }) => new Uint8Array(nullifier).toString()
),
]);

/** @type {(entry: WalletCacheNote) => WalletCacheDbNote} */
const toDbNote = (entry) => ({
...entry,
note: entry.note.buffer,
nullifier: entry.nullifier.buffer,
});

describe("fillCacheDatabase", () => {
beforeEach(async () => {
await getCacheDatabase().delete();
});

it("should fill the database tables with mock data", async () => {
const expectedPendingNotesInfo = sortByNullifier(cachePendingNotesInfo);
const expectedSpentNotes = sortByNullifier(cacheSpentNotes);
const expectedUnspentNotes = sortByNullifier(cacheUnspentNotes);
const expectedPendingNotesInfo = sortByNullifier(cachePendingNotesInfo).map(
(v) => ({
...v,
nullifier: v.nullifier.buffer,
})
);
const expectedSpentNotes = sortByNullifier(cacheSpentNotes).map(toDbNote);
const expectedUnspentNotes =
sortByNullifier(cacheUnspentNotes).map(toDbNote);

await fillCacheDatabase();

Expand All @@ -26,16 +53,16 @@ describe("fillCacheDatabase", () => {
await db.open();

await expect(
db.table("pendingNotesInfo").toArray().then(sortByNullifier)
db.table("pendingNotesInfo").toArray().then(sortByDbNullifier)
).resolves.toStrictEqual(expectedPendingNotesInfo);
await expect(
db.table("spentNotes").toArray().then(sortByNullifier)
db.table("spentNotes").toArray().then(sortByDbNullifier)
).resolves.toStrictEqual(expectedSpentNotes);
await expect(db.table("syncInfo").toArray()).resolves.toStrictEqual(
cacheSyncInfo
);
await expect(
db.table("unspentNotes").toArray().then(sortByNullifier)
db.table("unspentNotes").toArray().then(sortByDbNullifier)
).resolves.toStrictEqual(expectedUnspentNotes);

db.close();
Expand Down
27 changes: 24 additions & 3 deletions web-wallet/src/lib/test-helpers/fillCacheDatabase.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { mapWith } from "lamb";
import { getCacheDatabase } from ".";

import {
Expand All @@ -7,6 +8,24 @@ import {
cacheUnspentNotes,
} from "$lib/mock-data";

/**
* In IndexedDB if we write a Uint8Array, we get
* back an ArrayBuffer when we retrieve the data.
*
* In `fake-indexeddb` this is not the case, so
* we intentionally write ArrayBuffers from the
* beginning.
*/
const fixPending = mapWith((record) => ({
...record,
nullifier: record.nullifier.buffer,
}));
const fixNotes = mapWith((record) => ({
...record,
note: record.note.buffer,
nullifier: record.nullifier.buffer,
}));

/** @type {() => Promise<void>} */
async function fillCacheDatabase() {
const db = getCacheDatabase();
Expand All @@ -18,10 +37,12 @@ async function fillCacheDatabase() {
"rw",
["pendingNotesInfo", "spentNotes", "syncInfo", "unspentNotes"],
async () => {
await db.table("pendingNotesInfo").bulkPut(cachePendingNotesInfo);
await db.table("spentNotes").bulkPut(cacheSpentNotes);
await db
.table("pendingNotesInfo")
.bulkPut(fixPending(cachePendingNotesInfo));
await db.table("spentNotes").bulkPut(fixNotes(cacheSpentNotes));
await db.table("syncInfo").bulkPut(cacheSyncInfo);
await db.table("unspentNotes").bulkPut(cacheUnspentNotes);
await db.table("unspentNotes").bulkPut(fixNotes(cacheUnspentNotes));
}
)
.finally(() => {
Expand Down
14 changes: 12 additions & 2 deletions web-wallet/src/lib/wallet-cache/__tests__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
filterWith,
getKey,
mapValues,
mapWith,
partitionWith,
pluckFrom,
setKey,
Expand Down Expand Up @@ -453,9 +454,18 @@ describe("Wallet cache", () => {
.equals(addressWithPendingNotes)
.and(
(note) =>
!pendingNullifiersAsStrings.includes(note.nullifier.toString())
!pendingNullifiersAsStrings.includes(
new Uint8Array(note.nullifier).toString()
)
)
.toArray();
.toArray()
.then(
mapWith((entry) => ({
...entry,
note: new Uint8Array(entry.note),
nullifier: new Uint8Array(entry.nullifier),
}))
);

db.close();
});
Expand Down
37 changes: 32 additions & 5 deletions web-wallet/src/lib/wallet-cache/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
setKey,
skipIf,
unless,
updateKey,
when,
} from "lamb";

Expand All @@ -29,6 +30,22 @@ import notesArrayToMap from "$lib/wallet/notesArrayToMap";
/** @type {(profiles: Array<import("$lib/vendor/w3sper.js/src/mod").Profile>) => string[]} */
const getAddressesFrom = mapWith(compose(String, getKey("address")));

/** @type {(buffer: ArrayBuffer) => Uint8Array} */
const bufferToUint8Array = (buffer) => new Uint8Array(buffer);

/** @type {(source: WalletCacheDbNote) => Omit<WalletCacheDbNote, "note"> & { note: Uint8Array }} */
const updateNote = updateKey("note", bufferToUint8Array);

const updateNullifier = updateKey("nullifier", bufferToUint8Array);

/** @type {(v: WalletCacheDbPendingNoteInfo[]) => WalletCachePendingNoteInfo[]} */
const restorePendingInfo = mapWith(updateNullifier);

/** @type {(v: WalletCacheDbNote[]) => WalletCacheNote[]} */
const restoreNotes = mapWith(compose(updateNullifier, updateNote));

const restoreNullifiers = mapWith(bufferToUint8Array);

/** @type {(rawCriteria: RawCriteria) => Criteria} */
const toCriteria = pipe([
skipIf(isUndefined),
Expand Down Expand Up @@ -140,23 +157,29 @@ class WalletCache {
* @returns {Promise<WalletCachePendingNoteInfo[]>}
*/
getPendingNotesInfo(nullifiers) {
return this.#getEntriesFrom("pendingNotesInfo", false, { nullifiers });
return this.#getEntriesFrom("pendingNotesInfo", false, { nullifiers }).then(
restorePendingInfo
);
}

/**
* @param {string[]} [addresses] Base58 encoded addresses to fetch the spent notes of
* @returns {Promise<WalletCacheNote[]>}
*/
getSpentNotes(addresses) {
return this.#getEntriesFrom("spentNotes", false, { addresses });
return this.#getEntriesFrom("spentNotes", false, { addresses }).then(
restoreNotes
);
}

/**
* @param {string[]} [addresses] Base58 encoded addresses to fetch the spent notes of
* @returns {Promise<Uint8Array[]>}
*/
getSpentNotesNullifiers(addresses) {
return this.#getEntriesFrom("spentNotes", true, { addresses });
return this.#getEntriesFrom("spentNotes", true, { addresses }).then(
restoreNullifiers
);
}

/** @returns {Promise<WalletCacheSyncInfo>} */
Expand All @@ -171,15 +194,19 @@ class WalletCache {
* @returns {Promise<WalletCacheNote[]>}
*/
getUnspentNotes(addresses) {
return this.#getEntriesFrom("unspentNotes", false, { addresses });
return this.#getEntriesFrom("unspentNotes", false, { addresses }).then(
restoreNotes
);
}

/**
* @param {string[]} [addresses] Base58 encoded addresses to fetch the unspent notes of
* @returns {Promise<Uint8Array[]>}
*/
getUnspentNotesNullifiers(addresses) {
return this.#getEntriesFrom("unspentNotes", true, { addresses });
return this.#getEntriesFrom("unspentNotes", true, { addresses }).then(
restoreNullifiers
);
}

/**
Expand Down
18 changes: 15 additions & 3 deletions web-wallet/src/lib/wallet-cache/wallet-cache.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ type WalletCacheNote = {
nullifier: Uint8Array;
};

type WalletCacheDbNote = Omit<WalletCacheNote, "note" | "nullifier"> & {
note: ArrayBuffer;
nullifier: ArrayBuffer;
};

type WalletCacheGetDataType<T extends WalletCacheTableName> =
T extends "pendingNotesInfo"
? WalletCachePendingNoteInfo[]
? WalletCacheDbPendingNoteInfo[]
: T extends "syncInfo"
? WalletCacheSyncInfo[]
: WalletCacheNote[];
: WalletCacheDbNote[];

type WalletCacheGetEntriesReturnType<
T extends WalletCacheTableName,
Expand All @@ -18,7 +23,7 @@ type WalletCacheGetEntriesReturnType<
? WalletCacheGetDataType<T>
: T extends "syncInfo"
? never
: Uint8Array[];
: ArrayBuffer[];

type WalletCacheHistoryEntry = {
history: Transaction[];
Expand All @@ -33,6 +38,13 @@ type WalletCachePendingNoteInfo = {
txId: string;
};

type WalletCacheDbPendingNoteInfo = Omit<
WalletCachePendingNoteInfo,
"nullifier"
> & {
nullifier: ArrayBuffer;
};

type WalletCacheSyncInfo = {
blockHeight: bigint;
bookmark: bigint;
Expand Down

0 comments on commit 9675f47

Please sign in to comment.