From: Chris Duncan Date: Sat, 26 Apr 2025 22:38:10 +0000 (-0700) Subject: Replace busy check timeout loop with promise queue. Working well but could use some... X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=refs%2Fheads%2Fnext%2Fmerge-work-functions-shared-code;p=nano-pow.git Replace busy check timeout loop with promise queue. Working well but could use some refactoring. --- diff --git a/src/lib/gpu/index.ts b/src/lib/gpu/index.ts index 0262e3d..44f6c69 100644 --- a/src/lib/gpu/index.ts +++ b/src/lib/gpu/index.ts @@ -15,6 +15,7 @@ export class NanoPowGpu { static #isInitialized: boolean = false static #busy: boolean = false static #debug: boolean = false + static #queue: any[] = [] static #device: GPUDevice | null = null static #bufferReset: BigUint64Array = new BigUint64Array([0n, 0n, 0n, 0n]) static #gpuBuffer: GPUBuffer @@ -30,8 +31,6 @@ export class NanoPowGpu { // Initialize WebGPU static async init (): Promise { console.log('Initializing NanoPowGpu.') - if (this.#busy) return - this.#busy = true // Request device and adapter try { if (navigator.gpu == null) throw new Error('WebGPU is not supported in this browser.') @@ -42,11 +41,9 @@ export class NanoPowGpu { device.lost?.then(this.reset) this.#device = device await this.setup() + this.#isInitialized = true } catch (err) { throw new Error('WebGPU initialization failed.', { cause: err }) - } finally { - this.#isInitialized = true - this.#busy = false } } @@ -145,17 +142,7 @@ export class NanoPowGpu { * Validate options and normalize its properties. */ static async #work_init (work: string | null, hash: string, options?: NanoPowOptions) { - if (this.#busy) { - console.log('NanoPowGpu is busy. Retrying search...') - return new Promise(r => { - setTimeout(async () => { - r(this.#work_init(work, hash, options)) - }, 100) - }) as Promise<{ difficulty: any, effort: any }> - } - if (this.#isInitialized === false) this.init() - this.#busy = true - + if (this.#isInitialized === false) await this.init() if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`) if (work != null && !/^[A-Fa-f0-9]{16}$/.test(work)) throw new TypeError(`Invalid work ${work}`) options ??= {} @@ -253,6 +240,26 @@ export class NanoPowGpu { if (this.#resultView == null) throw new Error(`Failed to get data from buffer.`) } + static async #work_queue (task: Function): Promise { + return new Promise((resolve, reject) => { + this.#queue.push({ task, resolve, reject }) + this.#work_process() + }) + } + + static #work_process () { + const next = this.#queue.shift() + if (next && !this.#busy) { + const { task, resolve, reject } = next + this.#busy = true + task().then(resolve).catch(reject).finally(() => { + console.log('==== DONE ====') + this.#busy = false + this.#work_process() + }) + } + } + /** * Finds a nonce that satisfies the Nano proof-of-work requirements. * @@ -260,10 +267,15 @@ export class NanoPowGpu { * @param {NanoPowOptions} options - Used to configure search execution */ static async work_generate (hash: string, options?: NanoPowOptions): Promise { + return this.#work_queue(() => this.#work_generate(hash, options)) + } + static async #work_generate (hash: string, options?: NanoPowOptions): Promise { + if (this.#debug) console.log('work_generate') const { difficulty, effort } = await this.#work_init(null, hash, options) let times = [] let start = performance.now() + let found = false let nonce = 0n let result = 0n let random = BigInt(Math.floor(Math.random() * 0xffffffff)) @@ -274,12 +286,11 @@ export class NanoPowGpu { seed = (seed & 0xffffffffn) << 32n | random if (this.#debug) console.log('seed', seed.toString(16).padStart(16, '0')) await this.#work_dispatch(this.#searchPipeline, seed, hash, difficulty, effort) - const found = !!this.#resultView.getUint32(0) + found = !!this.#resultView.getUint32(0) nonce = this.#resultView.getBigUint64(8, true) result = this.#resultView.getBigUint64(16, true) - this.#busy = !found times.push(performance.now() - start) - } while (this.#busy) + } while (!found) if (this.#debug) { console.log('nonce', nonce, nonce.toString(16).padStart(16, '0')) @@ -301,6 +312,10 @@ export class NanoPowGpu { * @param {NanoPowOptions} options - Options used to configure search execution */ static async work_validate (work: string, hash: string, options?: NanoPowOptions): Promise { + return this.#work_queue(() => this.#work_validate(work, hash, options)) + } + static async #work_validate (work: string, hash: string, options?: NanoPowOptions): Promise { + if (this.#debug) console.log('work_validate') const { difficulty } = await this.#work_init(work, hash, options) let result = 0n @@ -311,7 +326,6 @@ export class NanoPowGpu { await this.#work_dispatch(this.#validatePipeline, seed, hash, difficulty, 1) nonce = this.#resultView.getBigUint64(8, true) result = this.#resultView.getBigUint64(16, true) - this.#busy = false if (seed !== nonce) throw new Error('Result does not match work') if (this.#debug) { diff --git a/test/index.html b/test/index.html index b84a6a8..31d21f8 100644 --- a/test/index.html +++ b/test/index.html @@ -88,11 +88,6 @@ SPDX-License-Identifier: GPL-3.0-or-later console.log(`work_validate() output for good nonce 2 is ${result === true ? 'correct' : 'incorrect'}`) expect.push(result) - result = await NP.work_validate('326f310d629a8a98', '204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA', { difficulty: 0xffffffff00000000n, debug: isDebug }) - result = result.valid === '1' && result.valid_all === '1' && result.valid_receive === '1' - console.log(`work_validate() output for good max difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`) - expect.push(result) - result = await NP.work_validate('c5d5d6f7c5d6ccd1', '281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117', { debug: isDebug }) result = result.valid_all === '1' console.log(`work_validate() output for colliding nonce is ${result === true ? 'correct' : 'incorrect'}`) @@ -114,9 +109,14 @@ SPDX-License-Identifier: GPL-3.0-or-later console.log(`work_validate() output for bad nonce 2 is ${result === true ? 'correct' : 'incorrect'}`) expect.push(result) - result = await NP.work_validate('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { difficulty: 0xffffffff00000000n, debug: isDebug }) - result = result.valid === '0' && result.valid_all === '0' && result.valid_receive === '1' - console.log(`work_validate() output for bad max difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`) + try { + result = await NP.work_validate('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { difficulty: 0xfffffff800000001n, debug: isDebug }) + console.log('boo') + } catch (err) { + result = null + } + result = result === null + console.log(`work_validate() output for bad difficulty beyond max is ${result === true ? 'correct' : 'incorrect'}`) expect.push(result) result = await NP.work_validate('29a9ae0236990e2e', '32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F', { debug: isDebug }) @@ -129,11 +129,6 @@ SPDX-License-Identifier: GPL-3.0-or-later console.log(`work_validate() output for bad receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`) expect.push(result) - result = await NP.work_validate('e45835c3b291c3d1', '9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F', { difficulty: 0xffffffff00000000n, debug: isDebug }) - result = result.valid === '0' && result.valid_all === '1' && result.valid_receive === '1' - console.log(`work_validate() output for send difficulty nonce that does not meet custom difficulty is ${result === true ? 'correct' : 'incorrect'}`) - expect.push(result) - try { if (!expect.every(result => result)) throw new Error(`Validation is not working`) } catch (err) { @@ -162,7 +157,7 @@ SPDX-License-Identifier: GPL-3.0-or-later const check = await NP.work_validate(result.work, result.hash, { difficulty, debug: isDebug }) const isValid = (result.hash === hash && check.valid === '1') ? 'VALID' : 'INVALID' times.push(end - start) - const msg = `${isValid} [${result.work}] ${result.hash} (${end - start} ms)` + const msg = `${isValid} (${end - start} ms)\n${JSON.stringify(result, ' ', 2)}` if (isOutputShown) document.getElementById('output').innerHTML += `${msg}
` } document.getElementById('output').innerHTML += `
`