From: Chris Duncan <chris@zoso.dev>
Date: Tue, 7 Jan 2025 20:32:19 +0000 (-0800)
Subject: Merge redundant find function with calculate to form search function like PowGpu... 
X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=241498bce3fec5df836b51efb0bd10ba42124fa7;p=libnemo.git

Merge redundant find function with calculate to form search function like PowGpu. Implement more of a promise-based pipeline through PowGl calculations.
---

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')
 	}
 }