From eb2f4cc7b72ff09198d75475732427dd0a780f7b Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 23 Apr 2025 14:38:29 -0700 Subject: [PATCH] Initial merge of work call shared code. Runs terribly, needs troubleshooting. --- src/lib/gpu/index.ts | 175 +++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 90 deletions(-) diff --git a/src/lib/gpu/index.ts b/src/lib/gpu/index.ts index b9a8b13..0ec6119 100644 --- a/src/lib/gpu/index.ts +++ b/src/lib/gpu/index.ts @@ -29,6 +29,7 @@ export class NanoPowGpu { // Initialize WebGPU static async init (): Promise { + console.log('Initializing NanoPowGpu.') if (this.#busy) return this.#busy = true // Request device and adapter @@ -44,9 +45,9 @@ export class NanoPowGpu { } catch (err) { throw new Error('WebGPU initialization failed.', { cause: err }) } finally { + this.#isInitialized = true this.#busy = false } - this.#isInitialized = true } static async setup (): Promise { @@ -171,6 +172,70 @@ export class NanoPowGpu { console.table(averages) } + /** + * Validate work, if present, and blockhash. + * 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 (!/^[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 ??= {} + options.debug ??= false + options.difficulty ??= 0xfffffff800000000n + options.effort ??= 0x4 + + if (typeof options.effort !== 'number' + && (options.effort < 0x1 || options.effort > 0x20) + ) { + throw new TypeError(`Invalid effort ${options.effort}`) + } + const effort = options.effort + + if (typeof options.difficulty !== 'string' + && typeof options.difficulty !== 'bigint' + ) { + throw new TypeError(`Invalid difficulty ${options.difficulty}`) + } + const difficulty = typeof options.difficulty === 'string' + ? BigInt(`0x${options.difficulty}`) + : options.difficulty + + if (difficulty < 0x0n || difficulty > 0xfffffff800000000n) { + throw new TypeError(`Invalid difficulty ${options.difficulty}`) + } + + this.#debug = !!options.debug + if (this.#debug) { + console.log('blockhash', hash) + console.log(`options`, JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v)) + } + + // Ensure WebGPU is initialized before calculating + let loads = 0 + while (this.#device == null && loads++ < 20) { + await new Promise(resolve => { + setTimeout(resolve, 500) + }) + } + if (this.#device == null) { + this.#busy = false + throw new Error(`WebGPU device failed to load.`) + } + + return { difficulty, effort } + } + static async #dispatch (pipeline: GPUComputePipeline, seed: bigint, hash: string, difficulty: bigint, passes: number): Promise { if (this.#device == null) throw new Error(`WebGPU device failed to load.`) // Set up uniform buffer object @@ -227,48 +292,7 @@ export class NanoPowGpu { * @param {NanoPowOptions} options - Used to configure search execution */ static async work_generate (hash: string, options?: NanoPowOptions): Promise { - if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`) - if (this.#busy) { - console.log('NanoPowGpu is busy. Retrying search...') - return new Promise(resolve => { - setTimeout(async (): Promise => { - const result = this.work_generate(hash, options) - resolve(result) - }, 100) - }) - } - if (this.#isInitialized === false) this.init() - this.#busy = true - - if (typeof options?.difficulty === 'string') { - try { - options.difficulty = BigInt(`0x${options.difficulty}`) - } catch (err) { - throw new TypeError(`Invalid difficulty ${options.difficulty}`) - } - } - const difficulty = (typeof options?.difficulty !== 'bigint' || options.difficulty < 0n || options.difficulty > 0xffffffffffffffffn) - ? 0xfffffff800000000n - : options.difficulty - const effort = (typeof options?.effort !== 'number' || options.effort < 0x1 || options.effort > 0x20) - ? 0x800 - : options.effort * 0x100 - this.#debug = !!(options?.debug) - if (this.#debug) console.log('NanoPowGpu.work_generate()') - if (this.#debug) console.log('blockhash', hash) - if (this.#debug) console.log('search options', JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v)) - - // Ensure WebGPU is initialized before calculating - let loads = 0 - while (this.#device == null && loads++ < 20) { - await new Promise(resolve => { - setTimeout(resolve, 500) - }) - } - if (this.#device == null) { - this.#busy = false - throw new Error(`WebGPU device failed to load.`) - } + const { difficulty, effort } = await this.#work_init(null, hash, options) let times = [] let start = performance.now() @@ -288,9 +312,13 @@ export class NanoPowGpu { this.#busy = !found times.push(performance.now() - start) } while (this.#busy) - if (this.#debug) this.#logAverages(times) - if (this.#debug) console.log('nonce', nonce, nonce.toString(16).padStart(16, '0')) - if (this.#debug) console.log('result', result, result.toString(16).padStart(16, '0')) + + if (this.#debug) { + this.#logAverages(times) + console.log('nonce', nonce, nonce.toString(16).padStart(16, '0')) + console.log('result', result, result.toString(16).padStart(16, '0')) + } + return { hash, work: nonce.toString(16).padStart(16, '0'), @@ -306,46 +334,7 @@ export class NanoPowGpu { * @param {NanoPowOptions} options - Options used to configure search execution */ static async work_validate (work: string, hash: string, options?: NanoPowOptions): Promise { - if (!/^[A-Fa-f0-9]{16}$/.test(work)) throw new TypeError(`Invalid work ${work}`) - if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`) - if (this.#busy) { - console.log('NanoPowGpu is busy. Retrying validate...') - return new Promise(resolve => { - setTimeout(async (): Promise => { - const result = this.work_validate(work, hash, options) - resolve(result) - }, 100) - }) - } - if (this.#isInitialized === false) this.init() - this.#busy = true - - if (typeof options?.difficulty === 'string') { - try { - options.difficulty = BigInt(`0x${options.difficulty}`) - } catch (err) { - throw new TypeError(`Invalid difficulty ${options.difficulty}`) - } - } - const difficulty = (typeof options?.difficulty !== 'bigint' || options.difficulty < 0n || options.difficulty > 0xffffffffffffffffn) - ? 0xfffffff800000000n - : options.difficulty - this.#debug = !!(options?.debug) - if (this.#debug) console.log('NanoPowGpu.work_validate()') - if (this.#debug) console.log('blockhash', hash) - if (this.#debug) console.log('validate options', JSON.stringify(options, (k, v) => typeof v === 'bigint' ? v.toString(16) : v)) - - // Ensure WebGPU is initialized before calculating - let loads = 0 - while (this.#device == null && loads < 20) { - await new Promise(resolve => { - setTimeout(resolve, 500) - }) - } - if (this.#device == null) { - this.#busy = false - throw new Error(`WebGPU device failed to load.`) - } + const { difficulty } = await this.#work_init(work, hash, options) let result = 0n let nonce = 0n @@ -357,16 +346,22 @@ export class NanoPowGpu { result = this.#resultView.getBigUint64(16, true) this.#busy = false if (seed !== nonce) throw new Error('Result does not match work') - if (this.#debug) console.log('nonce', nonce, nonce.toString(16).padStart(16, '0')) - if (this.#debug) console.log('result', result, result.toString(16).padStart(16, '0')) + + if (this.#debug) { + console.log('nonce', nonce, nonce.toString(16).padStart(16, '0')) + console.log('result', result, result.toString(16).padStart(16, '0')) + } + const response: WorkValidateResponse = { hash, work: nonce.toString(16).padStart(16, '0'), difficulty: result.toString(16).padStart(16, '0'), valid_all: (result >= this.#SEND) ? '1' : '0', - valid_receive: (result >= this.#RECEIVE) ? '1' : '0', + valid_receive: (result >= this.#RECEIVE) ? '1' : '0' + } + if (options?.difficulty != null) { + response.valid = (result >= difficulty) ? '1' : '0' } - if (options?.difficulty != null) response.valid = (result >= difficulty) ? '1' : '0' return response } } -- 2.34.1