From: Chris Duncan Date: Tue, 15 Jul 2025 12:33:37 +0000 (-0700) Subject: Merge pool into workers barrel module. X-Git-Tag: v0.10.5~57^2~17 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=1f63d783d062f2ce4896b163081fdc47feb29e64;p=libnemo.git Merge pool into workers barrel module. --- diff --git a/package.json b/package.json index ff669d8..e166e50 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,7 @@ "imports": { "#dist/*": "./dist/*", "#src/*": "./src/*", - "#workers": "./src/lib/workers/index.js", - "#workers/*": "./src/lib/workers/*" + "#workers": "./src/lib/workers/index.js" }, "dependencies": { "nano-pow": "^5.1.4" diff --git a/src/lib/account.ts b/src/lib/account.ts index 6da9542..0215b18 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -4,9 +4,8 @@ import { Blake2b } from './blake2b' import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants' import { base32, bytes, hex, obj, utf8 } from './convert' -import { Queue } from './pool' import { Rpc } from './rpc' -import { NanoNaClWorker, SafeWorker } from '#workers' +import { NanoNaClWorker, Queue, SafeWorker } from '#workers' /** * Represents a single Nano address and the associated public key. To include the diff --git a/src/lib/block.ts b/src/lib/block.ts index 213c60c..6f4f078 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -6,9 +6,8 @@ import { Account } from './account' import { Blake2b } from './blake2b' import { BURN_ADDRESS, PREAMBLE, DIFFICULTY_RECEIVE, DIFFICULTY_SEND } from './constants' import { dec, hex } from './convert' -import { Queue } from './pool' import { Rpc } from './rpc' -import { NanoNaClWorker } from '#workers' +import { NanoNaClWorker, Queue } from '#workers' /** * Represents a block as defined by the Nano cryptocurrency protocol. The Block diff --git a/src/lib/pool.ts b/src/lib/pool.ts deleted file mode 100644 index 7b56fb7..0000000 --- a/src/lib/pool.ts +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Chris Duncan -// SPDX-License-Identifier: GPL-3.0-or-later - -export type Headers = { - [key: string]: any -} - -export type Data = { - [key: string]: ArrayBuffer -} - -type Task = { - id: number - headers: Headers | null - data?: Data - reject: (value: any) => void - resolve: (value: any) => void -} - -/** -* Processes a queue of tasks using Web Workers. -*/ -export class Queue { - static #decoder: TextDecoder = new TextDecoder() - static #encoder: TextEncoder = new TextEncoder() - static #instances: Queue[] = [] - static get instances (): Queue[] { return this.#instances } - - #job?: Task - #isIdle: boolean - #queue: Task[] = [] - #url: string - #worker: Worker - - /** - * Creates a Web Worker from a stringified script. - * - * @param {string} worker - Stringified worker class body - * @param {number} [count=1] - Integer between 1 and CPU thread count shared among all Pools - */ - constructor (worker: string) { - this.#isIdle = true - this.#queue = [] - this.#url = URL.createObjectURL(new Blob([worker], { type: 'text/javascript' })) - this.#worker = new Worker(this.#url, { type: 'module' }) - this.#worker.addEventListener('message', message => { - let result = JSON.parse(Queue.#decoder.decode(message.data) || '[]') - if (!Array.isArray(result)) result = [result] - debugger - this.#report(result) - }) - Queue.#instances.push(this) - } - - async add (headers: Headers | null, data?: Data): Promise { - return await this.#assign(task => this.#queue.push(task), headers, data) - } - - async prioritize (headers: Headers | null, data?: Data): Promise { - return await this.#assign(task => this.#queue.unshift(task), headers, data) - } - - terminate (): void { - this.#job = undefined - this.#worker.terminate() - } - - async #assign (enqueue: (task: Task) => number, headers: Headers | null, data?: Data) { - return new Promise(async (resolve, reject): Promise => { - const task: Task = { - id: performance.now(), - headers, - data, - resolve, - reject - } - await enqueue(task) - debugger - if (this.#isIdle) this.#process() - }) - } - - #process = (): void => { - debugger - this.#job = this.#queue.shift() - if (this.#job == null) { - throw new Error('Failed to get job from empty task queue.') - } - const { id, headers, data, reject } = this.#job - this.#isIdle = !id - try { - const buffers: ArrayBuffer[] = [] - if (data != null) { - for (let d of Object.keys(data)) { - buffers.push(data[d]) - } - } - this.#worker.postMessage({ headers, data }, buffers) - } catch (err) { - reject(err) - } - } - - #report (results: any[]): void { - if (this.#job == null) { - throw new Error('Worker returned results but had nowhere to report it.') - } - const { resolve, reject } = this.#job - debugger - try { - resolve(results) - } catch (err) { - reject(err) - } finally { - this.#process() - } - } -} - -/** -* Provides basic worker event messaging to extending classes. -* -* In order to be properly bundled in a format that can be used to create an -* inline Web Worker, the extending classes must export WorkerInterface and -* themselves as a string: -*``` -* export default ` -* const WorkerInterface = ${WorkerInterface} -* const Pow = ${Pow} -* ` -* ``` -* They must also initialize the event listener by calling their inherited -* `listen()` function. Finally, they must override the implementation of the -* `work()` function. See the documentation of those functions for details. -*/ -export abstract class WorkerInterface { - /** - * Processes data through a worker. - * - * Extending classes must override this template by implementing the same - * function signature and providing their own processing call in the try-catch - * block. - * - * @param {Header} headers - Flat object of header data - * @param {any[]} data - Transferred buffer of data to process - * @returns Promise for processed data - */ - static async work (headers: Headers | null, data?: Data): Promise { - return new Promise(async (resolve, reject): Promise => { - try { - let x, y - if (headers != null) { - const { sample } = headers - x = sample - } - if (data != null) { - const { buf } = data - y = buf - } - resolve({ x, y }) - } catch (err) { - reject(err) - } - }) - } - - /** - * Encodes worker results as an ArrayBuffer so it can be transferred back to - * the main thread. - * - * @param {any[]} results - Array of processed data - */ - static report (results: any[]): void { - const buffer = new TextEncoder().encode(JSON.stringify(results)).buffer - //@ts-expect-error - postMessage(buffer, [buffer]) - } - - /** - * Listens for messages from the main thread. - * - * Extending classes must call this in a static initialization block: - * ``` - * static { - * Extension.listen() - * } - * ``` - */ - static listen (): void { - addEventListener('message', (message: any): void => { - const { name, headers, data } = message - if (name === 'STOP') { - close() - const buffer = new ArrayBuffer(0) - //@ts-expect-error - postMessage(buffer, [buffer]) - } else { - this.work(headers, data).then(this.report).catch(this.report) - } - }) - } -} diff --git a/src/lib/wallets/bip44-wallet.ts b/src/lib/wallets/bip44-wallet.ts index 21c2538..a5bc47c 100644 --- a/src/lib/wallets/bip44-wallet.ts +++ b/src/lib/wallets/bip44-wallet.ts @@ -6,8 +6,7 @@ import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js' import { SEED_LENGTH_BIP44 } from '#src/lib/constants.js' import { hex, utf8 } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' -import { Queue } from '#src/lib/pool.js' -import { Bip44CkdWorker } from '#workers' +import { Bip44CkdWorker, Queue } from '#workers' /** * Hierarchical deterministic (HD) wallet created by using a source of entropy to diff --git a/src/lib/wallets/index.ts b/src/lib/wallets/index.ts index b7161e4..ada0f7b 100644 --- a/src/lib/wallets/index.ts +++ b/src/lib/wallets/index.ts @@ -6,9 +6,8 @@ import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js' import { ADDRESS_GAP } from '#src/lib/constants.js' import { hex, utf8 } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' -import { Queue } from '#src/lib/pool.js' import { Rpc } from '#src/lib/rpc.js' -import { SafeWorker } from '#workers' +import { Queue, SafeWorker } from '#workers' export { Bip44Wallet } from './bip44-wallet' export { Blake2bWallet } from './blake2b-wallet' diff --git a/src/lib/wallets/ledger-wallet.ts b/src/lib/wallets/ledger-wallet.ts index 6601020..2401659 100644 --- a/src/lib/wallets/ledger-wallet.ts +++ b/src/lib/wallets/ledger-wallet.ts @@ -5,12 +5,12 @@ import { ledgerUSBVendorId } from '@ledgerhq/devices' import { default as TransportBLE } from '@ledgerhq/hw-transport-web-ble' import { default as TransportUSB } from '@ledgerhq/hw-transport-webusb' import { default as TransportHID } from '@ledgerhq/hw-transport-webhid' +import { KeyPair, Wallet } from '.' import { ChangeBlock, ReceiveBlock, SendBlock } from '#src/lib/block.js' import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, LEDGER_ADPU_CODES, LEDGER_STATUS_CODES } from '#src/lib/constants.js' import { bytes, dec, hex } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' import { Rpc } from '#src/lib/rpc.js' -import { KeyPair, Wallet } from '.' type DeviceStatus = 'DISCONNECTED' | 'BUSY' | 'LOCKED' | 'CONNECTED' diff --git a/src/lib/workers/bip44-ckd.ts b/src/lib/workers/bip44-ckd.ts index ae2e939..7d634f6 100644 --- a/src/lib/workers/bip44-ckd.ts +++ b/src/lib/workers/bip44-ckd.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later -import { Data, Headers, WorkerInterface } from '#src/lib/pool.js' + +import { Data, Headers, WorkerInterface } from '.' type ExtendedKey = { privateKey: DataView diff --git a/src/lib/workers/index.ts b/src/lib/workers/index.ts index b9bd85e..f2b9d2e 100644 --- a/src/lib/workers/index.ts +++ b/src/lib/workers/index.ts @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2025 Chris Duncan // SPDX-License-Identifier: GPL-3.0-or-later + import { default as Bip44CkdWorker, Bip44Ckd } from './bip44-ckd' import { default as NanoNaClWorker, NanoNaCl } from './nano-nacl' import { default as SafeWorker, Safe } from './safe' @@ -12,3 +13,203 @@ export { Safe, SafeWorker } + +export type Headers = { + [key: string]: any +} + +export type Data = { + [key: string]: ArrayBuffer +} + +type Task = { + id: number + headers: Headers | null + data?: Data + reject: (value: any) => void + resolve: (value: any) => void +} + +/** +* Processes a queue of tasks using Web Workers. +*/ +export class Queue { + static #decoder: TextDecoder = new TextDecoder() + static #encoder: TextEncoder = new TextEncoder() + static #instances: Queue[] = [] + static get instances (): Queue[] { return this.#instances } + + #job?: Task + #isIdle: boolean + #queue: Task[] = [] + #url: string + #worker: Worker + + /** + * Creates a Web Worker from a stringified script. + * + * @param {string} worker - Stringified worker class body + * @param {number} [count=1] - Integer between 1 and CPU thread count shared among all Pools + */ + constructor (worker: string) { + this.#isIdle = true + this.#queue = [] + this.#url = URL.createObjectURL(new Blob([worker], { type: 'text/javascript' })) + this.#worker = new Worker(this.#url, { type: 'module' }) + this.#worker.addEventListener('message', message => { + let result = JSON.parse(Queue.#decoder.decode(message.data) || '[]') + if (!Array.isArray(result)) result = [result] + debugger + this.#report(result) + }) + Queue.#instances.push(this) + } + + async add (headers: Headers | null, data?: Data): Promise { + return await this.#assign(task => this.#queue.push(task), headers, data) + } + + async prioritize (headers: Headers | null, data?: Data): Promise { + return await this.#assign(task => this.#queue.unshift(task), headers, data) + } + + terminate (): void { + this.#job = undefined + this.#worker.terminate() + } + + async #assign (enqueue: (task: Task) => number, headers: Headers | null, data?: Data) { + return new Promise(async (resolve, reject): Promise => { + const task: Task = { + id: performance.now(), + headers, + data, + resolve, + reject + } + await enqueue(task) + debugger + if (this.#isIdle) this.#process() + }) + } + + #process = (): void => { + debugger + this.#job = this.#queue.shift() + if (this.#job == null) { + throw new Error('Failed to get job from empty task queue.') + } + const { id, headers, data, reject } = this.#job + this.#isIdle = !id + try { + const buffers: ArrayBuffer[] = [] + if (data != null) { + for (let d of Object.keys(data)) { + buffers.push(data[d]) + } + } + this.#worker.postMessage({ headers, data }, buffers) + } catch (err) { + reject(err) + } + } + + #report (results: any[]): void { + if (this.#job == null) { + throw new Error('Worker returned results but had nowhere to report it.') + } + const { resolve, reject } = this.#job + debugger + try { + resolve(results) + } catch (err) { + reject(err) + } finally { + this.#process() + } + } +} + +/** +* Provides basic worker event messaging to extending classes. +* +* In order to be properly bundled in a format that can be used to create an +* inline Web Worker, the extending classes must export WorkerInterface and +* themselves as a string: +*``` +* export default ` +* const WorkerInterface = ${WorkerInterface} +* const Pow = ${Pow} +* ` +* ``` +* They must also initialize the event listener by calling their inherited +* `listen()` function. Finally, they must override the implementation of the +* `work()` function. See the documentation of those functions for details. +*/ +export abstract class WorkerInterface { + /** + * Processes data through a worker. + * + * Extending classes must override this template by implementing the same + * function signature and providing their own processing call in the try-catch + * block. + * + * @param {Header} headers - Flat object of header data + * @param {any[]} data - Transferred buffer of data to process + * @returns Promise for processed data + */ + static async work (headers: Headers | null, data?: Data): Promise { + return new Promise(async (resolve, reject): Promise => { + try { + let x, y + if (headers != null) { + const { sample } = headers + x = sample + } + if (data != null) { + const { buf } = data + y = buf + } + resolve({ x, y }) + } catch (err) { + reject(err) + } + }) + } + + /** + * Encodes worker results as an ArrayBuffer so it can be transferred back to + * the main thread. + * + * @param {any[]} results - Array of processed data + */ + static report (results: any[]): void { + const buffer = new TextEncoder().encode(JSON.stringify(results)).buffer + //@ts-expect-error + postMessage(buffer, [buffer]) + } + + /** + * Listens for messages from the main thread. + * + * Extending classes must call this in a static initialization block: + * ``` + * static { + * Extension.listen() + * } + * ``` + */ + static listen (): void { + addEventListener('message', (message: any): void => { + const { name, headers, data } = message + if (name === 'STOP') { + close() + const buffer = new ArrayBuffer(0) + //@ts-expect-error + postMessage(buffer, [buffer]) + } else { + this.work(headers, data).then(this.report).catch(this.report) + } + }) + } +} diff --git a/src/lib/workers/nano-nacl.ts b/src/lib/workers/nano-nacl.ts index 928505f..3aa624c 100644 --- a/src/lib/workers/nano-nacl.ts +++ b/src/lib/workers/nano-nacl.ts @@ -3,8 +3,8 @@ 'use strict' +import { Data, Headers, WorkerInterface } from '.' import { Blake2b } from '#src/lib/blake2b.js' -import { Data, Headers, WorkerInterface } from '#src/lib/pool.js' // Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. // Public domain. diff --git a/src/lib/workers/safe.ts b/src/lib/workers/safe.ts index afc4957..464548a 100644 --- a/src/lib/workers/safe.ts +++ b/src/lib/workers/safe.ts @@ -3,9 +3,9 @@ 'use strict' +import { Data, Headers, WorkerInterface } from '.' import { bytes, hex, utf8, default as Convert } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' -import { Data, Headers, WorkerInterface } from '#src/lib/pool.js' type SafeRecord = { encrypted: string,