From 73e41c8032cf4cf7cf905fe7caf9301ab1fb34eb Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Sat, 14 Sep 2024 11:06:12 +0200 Subject: [PATCH] feat: add service worker and logic to download & validate MASP params --- apps/namadillo/package.json | 5 +- apps/namadillo/scripts/startProxies.js | 23 +--- apps/namadillo/src/index.tsx | 13 +++ apps/namadillo/sw/constants.ts | 20 ++++ apps/namadillo/sw/indexedDb.ts | 153 +++++++++++++++++++++++++ apps/namadillo/sw/sw.ts | 71 ++++++++++++ apps/namadillo/tsconfig.sw.json | 13 +++ 7 files changed, 274 insertions(+), 24 deletions(-) create mode 100644 apps/namadillo/sw/constants.ts create mode 100644 apps/namadillo/sw/indexedDb.ts create mode 100644 apps/namadillo/sw/sw.ts create mode 100644 apps/namadillo/tsconfig.sw.json diff --git a/apps/namadillo/package.json b/apps/namadillo/package.json index 9432e8996..a8601cc29 100644 --- a/apps/namadillo/package.json +++ b/apps/namadillo/package.json @@ -43,7 +43,7 @@ "release:dry-run": "release-it --verbose --dry-run --ci", "release:no-npm": "release-it --verbose --no-npm.publish --ci", "start:proxy": "node ./scripts/startProxies.js", - "dev": "vite", + "dev": "tsc --watch --project tsconfig.sw.json& vite", "preview": "vite preview", "dev:local": "NODE_ENV=development NAMADA_INTERFACE_LOCAL=\"true\" yarn dev", "dev:proxy": "NAMADA_INTERFACE_PROXY=true && ./scripts/start-proxies.sh && yarn dev:local", @@ -57,7 +57,6 @@ "test:watch": "yarn wasm:build:test && yarn jest --watchAll=true", "test:coverage": "yarn wasm:build:test && yarn test --coverage", "test:ci": "jest", - "test:watch-only": "yarn jest --watchAll=true", "e2e-test": "PLAYWRIGHT_BASE_URL=http://localhost:3000 yarn playwright test", "e2e-test:headed": "PLAYWRIGHT_BASE_URL=http://localhost:3000 yarn playwright test --project=chromium --headed", "wasm:build": "node ./scripts/build.js --release", @@ -107,12 +106,10 @@ "eslint-plugin-react-hooks": "^4.6.0", "globals": "^15.9.0", "history": "^5.3.0", - "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-create-mock-instance": "^2.0.0", "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", - "jest-transformer-svg": "^2.0.2", "local-cors-proxy": "^1.1.0", "postcss": "^8.4.32", "release-it": "^17.0.1", diff --git a/apps/namadillo/scripts/startProxies.js b/apps/namadillo/scripts/startProxies.js index 361592dd2..c0a49002e 100644 --- a/apps/namadillo/scripts/startProxies.js +++ b/apps/namadillo/scripts/startProxies.js @@ -1,31 +1,14 @@ const { exec } = require("child_process"); require("dotenv").config(); -const { - NAMADA_INTERFACE_NAMADA_ALIAS = "Namada", - NAMADA_INTERFACE_NAMADA_URL, - NAMADA_INTERFACE_COSMOS_ALIAS = "Cosmos", - NAMADA_INTERFACE_COSMOS_URL, - NAMADA_INTERFACE_ETH_ALIAS = "Ethereum", - NAMADA_INTERFACE_ETH_URL, -} = process.env; +const { TRUSTED_SETUP_URL } = process.env; const proxyConfigs = [ { - alias: NAMADA_INTERFACE_NAMADA_ALIAS, - url: NAMADA_INTERFACE_NAMADA_URL, + alias: "Trusted-Setup GitHub URL", + url: TRUSTED_SETUP_URL, proxyPort: 8010, }, - { - alias: NAMADA_INTERFACE_COSMOS_ALIAS, - url: NAMADA_INTERFACE_COSMOS_URL, - proxyPort: 8011, - }, - { - alias: NAMADA_INTERFACE_ETH_ALIAS, - url: NAMADA_INTERFACE_ETH_URL, - proxyPort: 8012, - }, ]; proxyConfigs.forEach(({ alias, url, proxyPort }) => { diff --git a/apps/namadillo/src/index.tsx b/apps/namadillo/src/index.tsx index ed0a25a27..cc7ddae1f 100644 --- a/apps/namadillo/src/index.tsx +++ b/apps/namadillo/src/index.tsx @@ -40,3 +40,16 @@ if (container) { ); }); } + +if ("serviceWorker" in navigator) { + window.addEventListener("load", () => { + navigator.serviceWorker + .register("/sw.js", { scope: "/" }) + .then((registration) => { + console.log("Service Worker registered: ", registration); + }) + .catch((error) => { + console.log("Service Worker registration failed: ", error); + }); + }); +} diff --git a/apps/namadillo/sw/constants.ts b/apps/namadillo/sw/constants.ts new file mode 100644 index 000000000..eee5cf694 --- /dev/null +++ b/apps/namadillo/sw/constants.ts @@ -0,0 +1,20 @@ +/** + * Define constants for MASP Params Storage + */ +const STORAGE_PREFIX = "/namadillo"; + +enum MaspParam { + Output = "masp-output.params", + Convert = "masp-convert.params", + Spend = "masp-spend.params", +} + +const MASP_PARAM_LEN: Record = { + [MaspParam.Output]: 16398620, + [MaspParam.Spend]: 49848572, + [MaspParam.Convert]: 22570940, +}; + +enum MsgType { + FetchParam = "fetch-params", +} diff --git a/apps/namadillo/sw/indexedDb.ts b/apps/namadillo/sw/indexedDb.ts new file mode 100644 index 000000000..c0e6ead36 --- /dev/null +++ b/apps/namadillo/sw/indexedDb.ts @@ -0,0 +1,153 @@ +/** + * Establish types for IndexedDB + */ +interface KVStore { + get(key: string): Promise; + set(key: string, data: U | null): Promise; + prefix(): string; +} + +let durability: IDBTransactionMode = "readwrite"; + +class IndexedDBKVStore implements KVStore { + protected cachedDB?: IDBDatabase; + + constructor(protected readonly _prefix: string) { } + + public async get(key: string): Promise { + const tx = (await this.getDB()).transaction([this.prefix()], "readonly"); + const store = tx.objectStore(this.prefix()); + + return new Promise((resolve, reject) => { + const request = store.get(key); + request.onerror = (event) => { + event.stopPropagation(); + + reject(event.target); + }; + request.onsuccess = () => { + if (!request.result) { + resolve(undefined); + } else { + resolve(request.result.data); + } + }; + }); + } + + public async set(key: string, data: U | null): Promise { + if (data === null) { + const tx = (await this.getDB()).transaction([this.prefix()], durability, { + durability: "strict", + }); + const store = tx.objectStore(this.prefix()); + + return new Promise((resolve, reject) => { + const request = store.delete(key); + request.onerror = (event) => { + event.stopPropagation(); + + reject(event.target); + }; + request.onsuccess = () => { + resolve(); + }; + }); + } else { + const tx = (await this.getDB()).transaction([this.prefix()], durability, { + durability: "strict", + }); + const store = tx.objectStore(this.prefix()); + + return new Promise((resolve, reject) => { + const request = store.put({ + key, + data, + }); + request.onerror = (event) => { + event.stopPropagation(); + + reject(event.target); + }; + request.onsuccess = () => { + resolve(); + }; + }); + } + } + + public prefix(): string { + return this._prefix; + } + + protected async getDB(): Promise { + if (this.cachedDB) { + return this.cachedDB; + } + + return new Promise((resolve, reject) => { + const request = indexedDB.open(this.prefix()); + request.onerror = (event) => { + event.stopPropagation(); + reject(event.target); + }; + + request.onupgradeneeded = (event) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const db = event.target.result; + + db.createObjectStore(this.prefix(), { keyPath: "key" }); + }; + + request.onsuccess = () => { + this.cachedDB = request.result; + resolve(request.result); + }; + }); + } + + public static async durabilityCheck(): Promise { + const { TARGET } = process.env; + let isDurable: boolean; + + if (TARGET === "chrome") { + durability = "readwrite"; + isDurable = true; + } else { + const prefix = "durability-check"; + const db: IDBDatabase = await new Promise((resolve, reject) => { + const request = indexedDB.open(prefix); + request.onerror = (event) => { + event.stopPropagation(); + reject(event.target); + }; + + request.onupgradeneeded = (event) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const db = event.target.result; + + db.createObjectStore(prefix, { keyPath: "key" }); + }; + + request.onsuccess = () => { + resolve(request.result); + }; + }); + + try { + db.transaction([prefix], "readwriteflush" as IDBTransactionMode, { + durability: "strict", + }); + durability = "readwriteflush" as IDBTransactionMode; + isDurable = true; + } catch { + durability = "readwrite"; + isDurable = false; + } + } + + return isDurable; + } +} diff --git a/apps/namadillo/sw/sw.ts b/apps/namadillo/sw/sw.ts new file mode 100644 index 000000000..5dc5e9faa --- /dev/null +++ b/apps/namadillo/sw/sw.ts @@ -0,0 +1,71 @@ +importScripts("indexedDb.js"); +importScripts("constants.js"); +// const { NAMADA_INTERFACE_PROXY: isProxy = false } = process.env; + +const isProxy = true; +const TRUSTED_SETUP_URL = + isProxy ? + "http://localhost:8010/proxy" + : "https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup"; + +const store = new IndexedDBKVStore(STORAGE_PREFIX); + +const fetchAndStoreMaspParam = async (maspParam: MaspParam): Promise => { + console.info(`Fetching ${maspParam}...`); + return fetch([TRUSTED_SETUP_URL, maspParam].join("/")) + .then(async (response) => { + if (response.ok) { + const reader = response.body?.getReader(); + if (!reader) { + throw new Error("No readable stream returned!"); + } + return new ReadableStream({ + start(controller) { + return pump(); + function pump() { + return reader?.read().then(({ done, value }) => { + // When no more data needs to be consumed, close the stream + if (done) { + controller.close(); + return; + } + // Enqueue the next data chunk into our target stream + controller.enqueue(value); + return pump(); + }); + } + }, + }); + } + }) + .then((stream) => new Response(stream)) + .then((response) => response.blob()) + .then(async (blob) => { + const arrayBuffer = await blob.arrayBuffer(); + const data = new Uint8Array(arrayBuffer); + + // Validate data length: + if (data.length === MASP_PARAM_LEN[maspParam]) { + console.info(`Storing ${maspParam} => `, data); + return store.set(maspParam, data); + } + return Promise.reject( + `Failed to download ${maspParam}: ${data.length} does not match ${MASP_PARAM_LEN[maspParam]}` + ); + }); +}; + +(async () => { + const maspOutputParam = await store.get(MaspParam.Output); + console.log("Found output?: ", maspOutputParam); + + const maspSpendParam = await store.get(MaspParam.Spend); + console.log("Found spend?: ", maspSpendParam); + + const maspConvertParam = await store.get(MaspParam.Convert); + console.log("Found convert?", maspConvertParam); + + if (!maspOutputParam) await fetchAndStoreMaspParam(MaspParam.Output); + if (!maspSpendParam) await fetchAndStoreMaspParam(MaspParam.Spend); + if (!maspConvertParam) await fetchAndStoreMaspParam(MaspParam.Convert); +})(); diff --git a/apps/namadillo/tsconfig.sw.json b/apps/namadillo/tsconfig.sw.json new file mode 100644 index 000000000..d02d2487e --- /dev/null +++ b/apps/namadillo/tsconfig.sw.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": "./sw", + "lib": ["ESNext", "WebWorker"], + "noEmit": false, + "strict": false, + "outDir": "public", + "target": "ESNext", + "isolatedModules": false + }, + "include": ["sw"] +}