From: Chris Duncan Date: Tue, 3 Dec 2024 20:25:23 +0000 (-0800) Subject: Convert IIFE to regular set of functions called from worker implementation. Use worke... X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=ddced1ea33885b97e81e85b2d216343beca434fd;p=libnemo.git Convert IIFE to regular set of functions called from worker implementation. Use worker-usable OffscreenCanvas for WebGL. Reorganize order of functions. Add typings and tabs. --- diff --git a/src/lib/pow.ts b/src/lib/pow.ts index 28f7eb3..24489d4 100644 --- a/src/lib/pow.ts +++ b/src/lib/pow.ts @@ -2,60 +2,112 @@ // SPDX-License-Identifier: GPL-3.0-or-later //@ts-nocheck import { Blake2b } from './blake2b.js' -// nano-webgl-pow -// Nano Currency Proof of Work Value generation using WebGL2 -// Author: numtel -// License: MIT - -// window.NanoWebglPow(hashHex, callback, progressCallback, threshold); -// @param hashHex String Previous Block Hash as Hex String -// @param callback Function Called when work value found -// Receives single string argument, work value as hex -// @param progressCallback Function Optional -// Receives single argument: n, number of frames so far -// Return true to abort -// @param threshold Number|String Optional difficulty threshold (default=0xFFFFFFF8 since v21) - -(function () { - - function array_hex (arr, index, length) { - let out = '' - for (let i = length - 1; i > -1; i--) { - out += (arr[i] > 15 ? '' : '0') + arr[i].toString(16) - } - return out - } - - function hex_reverse (hex) { - let out = '' - for (let i = hex.length; i > 0; i -= 2) { - out += hex.slice(i - 2, i) - } - return out - } - - function calculate (hashHex, callback, progressCallback, threshold = '0xFFFFFFF8') { - if (typeof threshold === 'number') threshold = '0x' + threshold.toString(16) - - const canvas = document.createElement('canvas') - - canvas.width = window.NanoWebglPow.width - canvas.height = window.NanoWebglPow.height - - const gl = canvas.getContext('webgl2') - - if (!gl) - throw new Error('webgl2_required') - - if (!/^[A-F-a-f0-9]{64}$/.test(hashHex)) - throw new Error('invalid_hash') - - gl.clearColor(0, 0, 0, 1) - - const reverseHex = hex_reverse(hashHex) - // Vertext Shader - const vsSource = `#version 300 es +const p = () => { + const NONCE_BYTES = 8 + const RECEIVE_THRESHOLD = '0xfffffe' + const SEND_THRESHOLD = '0xfffffff8' + + /** + * Listens for messages from a calling function. + */ + addEventListener('message', (message) => { + const data = JSON.parse(new TextDecoder().decode(message.data ?? message)) + for (const d of data) { + if (d === 'stop') { + close() + postMessage(new ArrayBuffer(0)) + } else { + find(d.hash, d.threshold ?? SEND_THRESHOLD).then(nonce => { + console.log(`pow found: ${nonce}`) + d.work = nonce + const buf = new TextEncoder().encode(JSON.stringify(data)).buffer + //@ts-expect-error + postMessage(buf, [buf]) + }) + } + } + }) + + async function find (hash: string, threshold: string = SEND_THRESHOLD): Promise { + console.log(`hash: ${hash}`) + return new Promise(resolve => { + calculate(hash, resolve, console.log, threshold) + }) + // let result = null + // let count = 0 + // do { + // count++ + // const nonce: Uint8Array = new Uint8Array(NONCE_BYTES) + // crypto.getRandomValues(nonce) + // const test: string = new Blake2b(NONCE_BYTES) + // .update(nonce) + // .update(parseHex(hash)) + // .digest('hex') as string + // if (count % 1000 === 0) console.log(`${count} hashes...`) + // if (BigInt(`0x${test}`) >= BigInt(`0x${threshold}`)) { + // result = nonce + // } + // } while (result == null) + // return result + } + + /** + * nano-webgl-pow + * Nano Currency Proof of Work Value generation using WebGL2 + * Author: numtel + * License: MIT + + * self.NanoWebglPow(hashHex, callback, progressCallback, threshold); + * @param hashHex String Previous Block Hash as Hex String + * @param callback Function Called when work value found + * Receives single string argument, work value as hex + * @param progressCallback Function Optional + * Receives single argument: n, number of frames so far + * Return true to abort + * @param threshold Number|String Optional difficulty threshold (default=0xFFFFFFF8 since v21) + */ + + // Both width and height must be multiple of 256, (one byte) + // but do not need to be the same, + // matching GPU capabilities is the aim + const webglWidth = 256 * 2 + const webglHeight = 256 * 2 + + function hexify (arr: number[] | Uint8Array): string { + let out = '' + for (let i = arr.length - 1; i >= 0; i--) { + out += arr[i].toString(16).padStart(2, '0') + } + return out + } + + function hex_reverse (hex: string): string { + let out = '' + for (let i = hex.length; i > 0; i -= 2) { + out += hex.slice(i - 2, i) + } + return out + } + + function calculate (hashHex, callback, progressCallback, threshold = '0xFFFFFFF8') { + if (typeof threshold === 'number') threshold = '0x' + threshold.toString(16) + + const canvas = new OffscreenCanvas(webglWidth, webglHeight) + const gl = canvas.getContext('webgl2') + + if (!gl) + throw new Error('webgl2_required') + + if (!/^[A-F-a-f0-9]{64}$/.test(hashHex)) + throw new Error(`invalid_hash ${hashHex}`) + + gl.clearColor(0, 0, 0, 1) + + const reverseHex = hex_reverse(hashHex) + + // Vertext Shader + const vsSource = `#version 300 es precision highp float; layout (location=0) in vec4 position; layout (location=1) in vec2 uv; @@ -67,8 +119,8 @@ import { Blake2b } from './blake2b.js' gl_Position = position; }` - // Fragment shader - const fsSource = `#version 300 es + // Fragment shader + const fsSource = `#version 300 es precision highp float; precision highp int; @@ -230,166 +282,105 @@ import { Blake2b } from './blake2b.js' } }` - const vertexShader = gl.createShader(gl.VERTEX_SHADER) - gl.shaderSource(vertexShader, vsSource) - gl.compileShader(vertexShader) - - if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) - throw gl.getShaderInfoLog(vertexShader) - - const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) - gl.shaderSource(fragmentShader, fsSource) - gl.compileShader(fragmentShader) - - if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) - throw gl.getShaderInfoLog(fragmentShader) - - const program = gl.createProgram() - gl.attachShader(program, vertexShader) - gl.attachShader(program, fragmentShader) - gl.linkProgram(program) - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - throw gl.getProgramInfoLog(program) - - gl.useProgram(program) - - // Construct simple 2D geometry - const triangleArray = gl.createVertexArray() - gl.bindVertexArray(triangleArray) - - // Vertex Positions, 2 triangles - const positions = new Float32Array([ - -1, -1, 0, -1, 1, 0, 1, 1, 0, - 1, -1, 0, 1, 1, 0, -1, -1, 0 - ]) - const positionBuffer = gl.createBuffer() - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) - gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW) - gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0) - gl.enableVertexAttribArray(0) - - // Texture Positions - const uvPosArray = new Float32Array([ - 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1 - ]) - const uvBuffer = gl.createBuffer() - gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer) - gl.bufferData(gl.ARRAY_BUFFER, uvPosArray, gl.STATIC_DRAW) - gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0) - gl.enableVertexAttribArray(1) - - const work0Location = gl.getUniformLocation(program, 'u_work0') - const work1Location = gl.getUniformLocation(program, 'u_work1') - - // Draw output until success or progressCallback says to stop - const work0 = new Uint8Array(4) - const work1 = new Uint8Array(4) - let n = 0 - - function draw () { - n++ - window.crypto.getRandomValues(work0) - window.crypto.getRandomValues(work1) - - gl.uniform4uiv(work0Location, Array.from(work0)) - gl.uniform4uiv(work1Location, Array.from(work1)) - - // Check with progressCallback every 100 frames - if (n % 100 === 0 && typeof progressCallback === 'function' && progressCallback(n)) - return - - gl.clear(gl.COLOR_BUFFER_BIT) - gl.drawArrays(gl.TRIANGLES, 0, 6) - const pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4) - gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels) - - // Check the pixels for any success - for (let i = 0; i < pixels.length; i += 4) { - if (pixels[i] !== 0) { - // Return the work value with the custom bits - typeof callback === 'function' && - callback( - array_hex(work1, 0, 4) + - array_hex([ - pixels[i + 2], - pixels[i + 3], - work0[2] ^ (pixels[i] - 1), - work0[3] ^ (pixels[i + 1] - 1) - ], 0, 4), n) - return - } - } - // Nothing found yet, try again - window.requestAnimationFrame(draw) - } - - // Begin generation - window.requestAnimationFrame(draw) - } - - window.NanoWebglPow = calculate - // Both width and height must be multiple of 256, (one byte) - // but do not need to be the same, - // matching GPU capabilities is the aim - window.NanoWebglPow.width = 256 * 2 - window.NanoWebglPow.height = 256 * 2 - -})() - - -const p = () => { - const NONCE_BYTES = 8 - const RECEIVE_THRESHOLD = 'fffffe0000000000' - const SEND_THRESHOLD = 'fffffff800000000' - - /** - * Listens for messages from a calling function. - */ - addEventListener('message', (message) => { - const data = JSON.parse(new TextDecoder().decode(message.data ?? message)) - if (data === 'stop') close() - for (const d of data) { - find(d.hash, d.threshold).then(nonce => { - console.log('pow found') - d.work = nonce - const buf = new TextEncoder().encode(JSON.stringify(data)).buffer - //@ts-expect-error - postMessage(buf, [buf]) - }) - } - }) - - async function find (hash: string, threshold: string = SEND_THRESHOLD) { - return new Promise((resolve, reject) => { - window.NanoWebglPow(hash, resolve, console.log) - - }) - // let result = null - // let count = 0 - // do { - // count++ - // const nonce: Uint8Array = new Uint8Array(NONCE_BYTES) - // crypto.getRandomValues(nonce) - // const test: string = new Blake2b(NONCE_BYTES) - // .update(nonce) - // .update(parseHex(hash)) - // .digest('hex') as string - // if (count % 1000 === 0) console.log(`${count} hashes...`) - // if (BigInt(`0x${test}`) >= BigInt(`0x${threshold}`)) { - // result = nonce - // } - // } while (result == null) - // return result - } - - function parseHex (hex: string) { - if (hex.length % 2 === 1) hex = `0${hex}` - const arr = hex.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) - return Uint8Array.from(arr ?? []) - } - - return { find } + const vertexShader = gl.createShader(gl.VERTEX_SHADER) + gl.shaderSource(vertexShader, vsSource) + gl.compileShader(vertexShader) + + if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) + throw gl.getShaderInfoLog(vertexShader) + + const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) + gl.shaderSource(fragmentShader, fsSource) + gl.compileShader(fragmentShader) + + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) + throw gl.getShaderInfoLog(fragmentShader) + + const program = gl.createProgram() + gl.attachShader(program, vertexShader) + gl.attachShader(program, fragmentShader) + gl.linkProgram(program) + + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + throw gl.getProgramInfoLog(program) + + gl.useProgram(program) + + // Construct simple 2D geometry + const triangleArray = gl.createVertexArray() + gl.bindVertexArray(triangleArray) + + // Vertex Positions, 2 triangles + const positions = new Float32Array([ + -1, -1, 0, -1, 1, 0, 1, 1, 0, + 1, -1, 0, 1, 1, 0, -1, -1, 0 + ]) + const positionBuffer = gl.createBuffer() + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) + gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW) + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0) + gl.enableVertexAttribArray(0) + + // Texture Positions + const uvPosArray = new Float32Array([ + 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1 + ]) + const uvBuffer = gl.createBuffer() + gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer) + gl.bufferData(gl.ARRAY_BUFFER, uvPosArray, gl.STATIC_DRAW) + gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0) + gl.enableVertexAttribArray(1) + + const work0Location = gl.getUniformLocation(program, 'u_work0') + const work1Location = gl.getUniformLocation(program, 'u_work1') + + // Draw output until success or progressCallback says to stop + const work0 = new Uint8Array(4) + const work1 = new Uint8Array(4) + let n = 0 + + function draw () { + n++ + crypto.getRandomValues(work0) + crypto.getRandomValues(work1) + + gl.uniform4uiv(work0Location, Array.from(work0)) + gl.uniform4uiv(work1Location, Array.from(work1)) + + // Check with progressCallback every 100 frames + if (n % 100 === 0 && typeof progressCallback === 'function' && progressCallback(n)) + return + + gl.clear(gl.COLOR_BUFFER_BIT) + gl.drawArrays(gl.TRIANGLES, 0, 6) + const pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4) + gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels) + + // Check the pixels for any success + for (let i = 0; i < pixels.length; i += 4) { + if (pixels[i] !== 0) { + // Return the work value with the custom bits + typeof callback === 'function' && + callback( + hexify(work1) + + hexify([ + pixels[i + 2], + pixels[i + 3], + work0[2] ^ (pixels[i] - 1), + work0[3] ^ (pixels[i + 1] - 1) + ]), n) + return + } + } + // Nothing found yet, try again + self.requestAnimationFrame(draw) + } + + // Begin generation + self.requestAnimationFrame(draw) + } + + return { find } } export const Pow = p()