From 07cb091d0e5e280aa82080ed1237baf0213b0803 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 2 Dec 2024 21:18:03 -0800 Subject: [PATCH] Fix promise thenable handling in pow. --- src/lib/pow.ts | 368 +++++++++++++++++++++++++------------------------ 1 file changed, 185 insertions(+), 183 deletions(-) diff --git a/src/lib/pow.ts b/src/lib/pow.ts index 01f3c8a..28f7eb3 100644 --- a/src/lib/pow.ts +++ b/src/lib/pow.ts @@ -18,44 +18,44 @@ import { Blake2b } from './blake2b.js' (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 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 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) + function calculate (hashHex, callback, progressCallback, threshold = '0xFFFFFFF8') { + if (typeof threshold === 'number') threshold = '0x' + threshold.toString(16) - const canvas = document.createElement('canvas') + const canvas = document.createElement('canvas') - canvas.width = window.NanoWebglPow.width - canvas.height = window.NanoWebglPow.height + canvas.width = window.NanoWebglPow.width + canvas.height = window.NanoWebglPow.height - const gl = canvas.getContext('webgl2') + const gl = canvas.getContext('webgl2') - if (!gl) - throw new Error('webgl2_required') + if (!gl) + throw new Error('webgl2_required') - if (!/^[A-F-a-f0-9]{64}$/.test(hashHex)) - throw new Error('invalid_hash') + if (!/^[A-F-a-f0-9]{64}$/.test(hashHex)) + throw new Error('invalid_hash') - gl.clearColor(0, 0, 0, 1) + gl.clearColor(0, 0, 0, 1) - const reverseHex = hex_reverse(hashHex) + const reverseHex = hex_reverse(hashHex) - // Vertext Shader - const vsSource = `#version 300 es + // Vertext Shader + const vsSource = `#version 300 es precision highp float; layout (location=0) in vec4 position; layout (location=1) in vec2 uv; @@ -67,8 +67,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,164 +230,166 @@ 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 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) { - d.nonce = find(d.hash, d.threshold) - console.log('pow found') - } - 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 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 } } export const Pow = p() -- 2.34.1