From 241498bce3fec5df836b51efb0bd10ba42124fa7 Mon Sep 17 00:00:00 2001 From: Chris Duncan <chris@zoso.dev> Date: Tue, 7 Jan 2025 12:32:19 -0800 Subject: [PATCH] Merge redundant find function with calculate to form search function like PowGpu. Implement more of a promise-based pipeline through PowGl calculations. --- src/lib/workers/powgl.ts | 142 +++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/src/lib/workers/powgl.ts b/src/lib/workers/powgl.ts index 1311fb1..6eea95b 100644 --- a/src/lib/workers/powgl.ts +++ b/src/lib/workers/powgl.ts @@ -18,7 +18,7 @@ export class PowGl extends WorkerInterface { return new Promise(async (resolve, reject): Promise<void> => { for (const d of data) { try { - d.work = await this.find(d.hash, d.threshold) + d.work = await this.search(d.hash, d.threshold) } catch (err) { reject(err) } @@ -27,18 +27,6 @@ export class PowGl extends WorkerInterface { }) } - /** - * Finds a nonce that satisfies the Nano proof-of-work requirements. - * - * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts - * @param {number} [threshold=0xfffffff8] - Difficulty of proof-of-work calculation - */ - static async find (hash: string, threshold: number = 0xfffffff8): Promise<string> { - return new Promise<string>(resolve => { - this.#calculate(hash, resolve, threshold) - }) - } - // Vertex Shader static #vsSource = `#version 300 es #pragma vscode_glsllint_stage: vert @@ -357,16 +345,22 @@ void main() { this.#query = this.#gl.createQuery() } - static #calculate (hashHex: string, callback: (nonce: string | PromiseLike<string>) => any, threshold: number): void { + /** + * Finds a nonce that satisfies the Nano proof-of-work requirements. + * + * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts + * @param {number} [threshold=0xfffffff8] - Difficulty of proof-of-work calculation + */ + static async search (hash: string, threshold: number = 0xfffffff8): Promise<string> { if (PowGl.#gl == null) throw new Error('WebGL 2 is required') - if (!/^[A-F-a-f0-9]{64}$/.test(hashHex)) throw new Error(`invalid_hash ${hashHex}`) + if (!/^[A-F-a-f0-9]{64}$/.test(hash)) throw new Error(`invalid_hash ${hash}`) if (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`) if (this.#gl == null) throw new Error('WebGL 2 is required') // Set up uniform buffer object const uboView = new DataView(new ArrayBuffer(144)) for (let i = 0; i < 64; i += 8) { - const uint32 = hashHex.slice(i, i + 8) + const uint32 = hash.slice(i, i + 8) uboView.setUint32(i * 2, parseInt(uint32, 16)) } uboView.setUint32(128, threshold, true) @@ -375,63 +369,77 @@ void main() { PowGl.#gl.bufferSubData(PowGl.#gl.UNIFORM_BUFFER, 0, uboView) PowGl.#gl.bindBuffer(PowGl.#gl.UNIFORM_BUFFER, null) - // Draw output until success or progressCallback says to stop + // Start drawing to calculate one nonce per pixel + let nonce = null const work = new Uint8Array(8) - let start: DOMHighResTimeStamp - const draw = (): void => { - if (PowGl.#gl == null) throw new Error('WebGL 2 is required') - if (PowGl.#query == null) throw new Error('WebGL 2 is required to run queries') - PowGl.#gl.clear(PowGl.#gl.COLOR_BUFFER_BIT) - - // Upload work buffer - crypto.getRandomValues(work) - PowGl.#gl.bindBuffer(PowGl.#gl.UNIFORM_BUFFER, PowGl.#workBuffer) - PowGl.#gl.bufferSubData(PowGl.#gl.UNIFORM_BUFFER, 0, Uint32Array.from(work)) - PowGl.#gl.bindBuffer(PowGl.#gl.UNIFORM_BUFFER, null) - - PowGl.#gl.beginQuery(PowGl.#gl.ANY_SAMPLES_PASSED_CONSERVATIVE, PowGl.#query) - PowGl.#gl.drawArrays(PowGl.#gl.TRIANGLES, 0, 6) - PowGl.#gl.endQuery(PowGl.#gl.ANY_SAMPLES_PASSED_CONSERVATIVE) - - requestAnimationFrame(checkQueryResult) + while (nonce == null) { + this.draw(work) + const found = await this.checkQueryResult() + if (found) { + nonce = this.readResult(work) + } } + return nonce + } - function checkQueryResult () { - if (PowGl.#gl == null) throw new Error('WebGL 2 is required to check query results') - if (PowGl.#query == null) throw new Error('Query not found') - if (PowGl.#gl.getQueryParameter(PowGl.#query, PowGl.#gl.QUERY_RESULT_AVAILABLE)) { - const anySamplesPassed = PowGl.#gl.getQueryParameter(PowGl.#query, PowGl.#gl.QUERY_RESULT) - if (anySamplesPassed) { - // A valid nonce was found - readBackResult() - } else { - // No valid nonce found, start the next draw call - requestAnimationFrame(draw) - } - } else { - // Query result not yet available, check again in the next frame - requestAnimationFrame(checkQueryResult) - } + static draw (work: Uint8Array): void { + if (this.#gl == null || this.#query == null) throw new Error('WebGL 2 is required to draw and query pixels') + if (this.#workBuffer == null) throw new Error('Work buffer is required to draw') + this.#gl.clear(this.#gl.COLOR_BUFFER_BIT) + + // Upload work buffer + crypto.getRandomValues(work) + this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#workBuffer) + this.#gl.bufferSubData(this.#gl.UNIFORM_BUFFER, 0, Uint32Array.from(work)) + this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null) + + this.#gl.beginQuery(this.#gl.ANY_SAMPLES_PASSED_CONSERVATIVE, this.#query) + this.#gl.drawArrays(this.#gl.TRIANGLES, 0, 6) + this.#gl.endQuery(this.#gl.ANY_SAMPLES_PASSED_CONSERVATIVE) + } + + static async checkQueryResult (): Promise<boolean> { + if (this.#gl == null || this.#query == null) throw new Error('WebGL 2 is required to check query results') + if (this.#gl.getQueryParameter(this.#query, this.#gl.QUERY_RESULT_AVAILABLE)) { + return this.#gl.getQueryParameter(this.#query, this.#gl.QUERY_RESULT) } - function readBackResult () { - if (PowGl.#gl == null) throw new Error('WebGL 2 is required to check read results') - PowGl.#gl.readPixels(0, 0, PowGl.#gl.drawingBufferWidth, PowGl.#gl.drawingBufferHeight, PowGl.#gl.RGBA, PowGl.#gl.UNSIGNED_BYTE, PowGl.#pixels) - // Check the pixels for any success - for (let i = 0; i < PowGl.#pixels.length; i += 4) { - if (PowGl.#pixels[i] !== 0) { - const hex = PowGl.#hexify(work.subarray(4, 8)) + PowGl.#hexify([ - PowGl.#pixels[i + 2], - PowGl.#pixels[i + 3], - work[2] ^ (PowGl.#pixels[i] - 1), - work[3] ^ (PowGl.#pixels[i + 1] - 1) - ]) - // Return the work value with the custom bits - typeof callback === 'function' && callback(hex) - return - } + // Query result not yet available, check again in the next frame + return new Promise((resolve, reject): void => { + try { + requestAnimationFrame(async (): Promise<void> => { + const result = await PowGl.checkQueryResult() + resolve(result) + }) + } catch (err) { + reject(err) + } + }) + } + + /** + * Reads pixels into the work buffer, checks every 4th pixel for the 'found' + * byte, converts the subsequent 3 pixels with the nonce byte values to a hex + * string, and returns the result. + * + * @param work - Buffer with the original random nonce value + * @returns Nonce as an 8-byte (16-char) hexadecimal string + */ + static readResult (work: Uint8Array): string { + if (this.#gl == null) throw new Error('WebGL 2 is required to read pixels') + this.#gl.readPixels(0, 0, this.#gl.drawingBufferWidth, this.#gl.drawingBufferHeight, this.#gl.RGBA, this.#gl.UNSIGNED_BYTE, this.#pixels) + for (let i = 0; i < this.#pixels.length; i += 4) { + if (this.#pixels[i] !== 0) { + // Return the work value with the custom bits + const hex = this.#hexify(work.subarray(4, 8)) + this.#hexify([ + this.#pixels[i + 2], + this.#pixels[i + 3], + work[2] ^ (this.#pixels[i] - 1), + work[3] ^ (this.#pixels[i + 1] - 1) + ]) + return hex } } - draw() + throw new Error('Query reported result but nonce value not found') } } -- 2.34.1