]> zoso.dev Git - nano-pow.git/commitdiff
Replace busy check timeout loop with promise queue. Working well but could use some... next/merge-work-functions-shared-code
authorChris Duncan <chris@zoso.dev>
Sat, 26 Apr 2025 22:38:10 +0000 (15:38 -0700)
committerChris Duncan <chris@zoso.dev>
Sat, 26 Apr 2025 22:38:10 +0000 (15:38 -0700)
src/lib/gpu/index.ts
test/index.html

index 0262e3d469ba24e26f5232d54c45a379a959ca46..44f6c69ded7ee30267f9e9cec59729c4ea30ddc3 100644 (file)
@@ -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<void> {
                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<any> {
+               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<WorkGenerateResponse> {
+               return this.#work_queue(() => this.#work_generate(hash, options))
+       }
+       static async #work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse> {
+               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<WorkValidateResponse> {
+               return this.#work_queue(() => this.#work_validate(work, hash, options))
+       }
+       static async #work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse> {
+               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) {
index b84a6a8d6a151acab5b0d8ab5f91aad68e3966cf..31d21f8949ad9127ac1961eda78a26e386dc1dbe 100644 (file)
@@ -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}<br/>`
                        }
                        document.getElementById('output').innerHTML += `<hr/>`