From: Chris Duncan Date: Sat, 1 Mar 2025 06:47:30 +0000 (-0800) Subject: Remove references to polluting global namespace. Update README. Delete unneeded bench... X-Git-Tag: v3.0.0~3 X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=8a44d6a743be9e31516229d9bb3ed9715ab3256a;p=nano-pow.git Remove references to polluting global namespace. Update README. Delete unneeded benchmarks. --- diff --git a/README.md b/README.md index 9b3c143..19244d1 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,6 @@ Use it directly on a webpage with a script module: ``` -The global namespace can be polluted by targeting `dist/global.min.js` although -this is not recommended: - -```html - -``` - ### Search ```javascript // `hash` is a 64-char hex string @@ -108,12 +101,9 @@ for receive/open/epoch blocks. The threshold is implemented in code as only the first 32 bits due to WGSL only supporting u32 integer types, but the result is the same. For example, if checking whether a two-digit number xx > 55, and it is known that the first -digit is 6, it is also automatically known that xx is greater than 55. - -The default threshold used is the send/change difficulty. Any other threshold -can be specified in practice. Also, NanoPow technically compares the nonce to -see if it is only greater than, and not necessarily equal to, the threshold, but -the difference is trivial for a range of 2⁶⁴-1 nonces. +digit is 6, it is also automatically known that xx is greater than 55. The +default threshold used is the send/change difficulty. Any other threshold can be +specified in practice. The BLAKE2b implementation has been optimized to the extreme for this package due to the very narrow use case to which it is applied. The compute shader is diff --git a/benchmarks-2.0.0.json b/benchmarks-2.0.0.json deleted file mode 100644 index 07cbc2b..0000000 --- a/benchmarks-2.0.0.json +++ /dev/null @@ -1,138 +0,0 @@ -Laptop, Firefox, WebGL -{ - "unusably slow" -} - -Laptop, Firefox (Nightly), WebGPU (something is wrong with this implementation) -{ - "NanoPow (WebGPU) | Effort: 2 | Dispatch: 262144 | Threads: 16777216": { - "count": 128, - "total": 25805, - "rate": 4.939462640490087, - "min": 198, - "max": 209, - "arithmetic": 201.6015625, - "truncated": 161.9609375, - "harmonic": 201.5619027743284, - "geometric": 201.5816342360767 - } -} - -Laptop, Firefox (Nightly), WebGL -{ - "unusably slow" -} - -Laptop, Chromium (Snapshot), WebGPU -{ - "NanoPow (WebGPU) | Effort: 2 | Dispatch: 262144 | Threads: 16777216": { - "count": 128, - "total": 10103.599999964237, - "rate": 12.632929507247823, - "min": 77.5, - "max": 87.30000001192093, - "arithmetic": 78.9343749997206, - "truncated": 63.32656250009313, - "harmonic": 78.9124533095342, - "geometric": 78.92324311597429 - } -} - -Laptop, Chromium (Snapshot), WebGL -{ - "unusably slow" -} - -iPhone 12, Safari, WebGPU -{ - "NanoPow (WebGPU) | Effort: 2 | Dispatch: 262144 | Threads: 16777216": { - "count": 128, - "total": 8765.000000000004, - "rate": 14.686825053995603, - "min": 67, - "max": 109, - "median": 68, - "arithmetic": 68.47656250000003, - "truncated": 68.08823529411801, - "harmonic": 68.35519958262564, - "geometric": 68.40675927078374 - } -} - -iPhone 12, Safari, WebGL -"crashes at 16 effort" - -RTX 3070, Firefox, WebGL (time below does not reflect unusably slow load time on every run) -{ - "NanoPow (WebGL) | Effort: 16 | Dispatch: 16777216 | Threads: 1073741824": { - "count": 128, - "total": 24143, - "rate": 5.411976111199197, - "min": 132, - "max": 299, - "arithmetic": 188.6171875, - "truncated": 147.8203125, - "harmonic": 186.31872639499562, - "geometric": 187.36393931536827 - } -} - -RTX 3070, Chromium (Snapshot), WebGL (time below does not reflect unusably slow load time on every run) -{ - "NanoPow (WebGL) | Effort: 16 | Dispatch: 16777216 | Threads: 1073741824": { - "count": 128, - "total": 47603.79999998212, - "rate": 3.064088523434683, - "min": 102.2000000178814, - "max": 2403.800000011921, - "arithmetic": 371.9046874998603, - "truncated": 261.08906249981374, - "harmonic": 318.718209547868, - "geometric": 334.06722019838764 - } -} - -RTX 3070, Chromium (Snapshot), WebGPU -{ - "NanoPow (WebGPU) | Effort: 2 | Dispatch: 262144 | Threads: 16777216": { - "count": 128, - "total": 1339.7999999821186, - "rate": 112.02275461545852, - "min": 8.100000023841858, - "max": 75.40000000596046, - "arithmetic": 10.467187499860302, - "truncated": 7.141406250419095, - "harmonic": 9.128520671295203, - "geometric": 9.399633647331045 - } -} - -RTX 3070, Firefox (Nightly), WebGL (time below does not reflect unusably slow load time on every run) -{ - "NanoPow (WebGL) | Effort: 16 | Dispatch: 16777216 | Threads: 1073741824": { - "count": 128, - "total": 35224.725607999215, - "rate": 3.7183400282472108, - "min": 136.09630800000195, - "max": 495.703582999995, - "arithmetic": 275.19316881249387, - "truncated": 215.14976949999706, - "harmonic": 242.32479721183498, - "geometric": 257.6746825273353 - } -} - -RTX 3070, Firefox (Nightly), WebGPU (something wrong with this implementation) -{ - "NanoPow (WebGPU) | Effort: 2 | Dispatch: 262144 | Threads: 16777216": { - "count": 128, - "total": 25629.320556000024, - "rate": 4.9683281060114695, - "min": 199.6132949999992, - "max": 209.03135699999984, - "arithmetic": 200.2290668437502, - "truncated": 161.01996142968767, - "harmonic": 200.2257252691799, - "geometric": 200.22737506193246 - } -} diff --git a/esbuild.mjs b/esbuild.mjs index be1bb8d..3a5d93b 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -6,8 +6,7 @@ import { glsl } from "esbuild-plugin-glsl" await build({ entryPoints: [ - { out: 'main.min', in: './src/main.js' }, - { out: 'global.min', in: './src/global.js' } + { out: 'main.min', in: './src/main.js' } ], outdir: 'dist', target: 'esnext', diff --git a/main.min.js b/main.min.js deleted file mode 100644 index f6091be..0000000 --- a/main.min.js +++ /dev/null @@ -1,963 +0,0 @@ -// src/shaders/compute.wgsl -var compute_default = "struct UBO{blockhash:array,2>,seed:vec2,threshold:u32};@group(0)@binding(0)var ubo:UBO;struct WORK{nonce:vec2,found:atomic};@group(0)@binding(1)varwork:WORK;const BLAKE2B_IV_0=vec2(0xF2BDC900u,0x6A09E667u);const Z=vec2(0u);const CARRY=vec4(1u,0u,1u,0u);const ROTATE_1=vec4(1u);const ROTATE_8=vec4(8u);const ROTATE_16=vec4(16u);const ROTATE_24=vec4(24u);const ROTATE_31=vec4(31u);var found:bool;@compute @workgroup_size(32)fn search(@builtin(global_invocation_id)global_id:vec3,@builtin(local_invocation_id)local_id:vec3){found=(local_id.x==0u&&atomicLoad(&work.found)!=0u);workgroupBarrier();if(found){return;}main(global_id);}@compute @workgroup_size(1)fn validate(@builtin(global_invocation_id)global_id:vec3){main(global_id);}fn main(id:vec3){let m0:vec2=ubo.seed ^ id.xy;let m1:vec2=ubo.blockhash[0u].xy;let m2:vec2=ubo.blockhash[0u].zw;let m3:vec2=ubo.blockhash[1u].xy;let m4:vec2=ubo.blockhash[1u].zw;var v01:vec4=vec4(BLAKE2B_IV_0,0x84CAA73Bu,0xBB67AE85u);var v23:vec4=vec4(0xFE94F82Bu,0x3C6EF372u,0x5F1D36F1u,0xA54FF53Au);var v45:vec4=vec4(0xADE682D1u,0x510E527Fu,0x2B3E6C1Fu,0x9B05688Cu);var v67:vec4=vec4(0xFB41BD6Bu,0x1F83D9ABu,0x137E2179u,0x5BE0CD19u);var v89:vec4=vec4(0xF3BCC908u,0x6A09E667u,0x84CAA73Bu,0xBB67AE85u);var vAB:vec4=vec4(0xFE94F82Bu,0x3C6EF372u,0x5F1D36F1u,0xA54FF53Au);var vCD:vec4=vec4(0xADE682F9u,0x510E527Fu,0x2B3E6C1Fu,0x9B05688Cu);var vEF:vec4=vec4(0x04BE4294u,0xE07C2654u,0x137E2179u,0x5BE0CD19u);var v56:vec4;var vFC:vec4;var v74:vec4;var vDE:vec4;var s0:vec4;var s1:vec4;s0=v01+v45;v01=s0+(vec4(s0(s1(s0(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1(s0>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1(s0>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1(s0>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vFC<>ROTATE_16)|(vDE<(s0(s1>ROTATE_31).yxwz|(v56<>ROTATE_31).yxwz|(v74<(s0(s1(s0(s1>ROTATE_24)|(v45<>ROTATE_24)|(v67<(s0(s1>ROTATE_16)|(vCD<>ROTATE_16)|(vEF<(s0(s1>ROTATE_31).yxwz|(v45<>ROTATE_31).yxwz|(v67<(s0(s1(s0(s0(s1>ROTATE_24)|(v56<>ROTATE_24)|(v74<(s0(s1>ROTATE_16)|(vDE<(s1ubo.threshold&&atomicLoad(&work.found)==0u){atomicStore(&work.found,1u);work.nonce=m0;}return;}"; - -// src/shaders/gl-downsample.ts -var NanoPowGlDownsampleShader = `#version 300 es -#pragma vscode_glsllint_stage: frag -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -precision highp int; - -out uvec4 nonce; - -// source texture to be downsampled -uniform highp usampler2D src; - -void main() { - nonce = uvec4(0u); - vec2 inputSize = vec2(textureSize(src, 0)); - vec2 texel = vec2(1.0) / inputSize; - vec2 blockCoord = (floor(gl_FragCoord.xy) * 2.0 + vec2(0.5)) / inputSize; - - uvec4 pixel = texture(src, blockCoord); - nonce = pixel.x == 0u ? nonce : pixel; - - pixel = texture(src, blockCoord + vec2(texel.x, 0.0)); - nonce = pixel.x == 0u ? nonce : pixel; - - pixel = texture(src, blockCoord + vec2(0.0, texel.y)); - nonce = pixel.x == 0u ? nonce : pixel; - - pixel = texture(src, blockCoord + vec2(texel.x, texel.y)); - nonce = pixel.x == 0u ? nonce : pixel; -} -`; - -// src/shaders/gl-draw.ts -var NanoPowGlDrawShader = `#version 300 es -#pragma vscode_glsllint_stage: frag -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif - -out uvec4 nonce; - -// blockhash - Array of precalculated block hash components -// threshold - 0xfffffff8 for send/change blocks, 0xfffffe00 for all else -// search - Checks all pixels if true, else only checks 1 pixel to validate -layout(std140) uniform UBO { - uint blockhash[8]; - uint threshold; - bool search; -}; - -// Random work seed values -layout(std140) uniform WORK { - uvec2 seed; -}; - -// Defined separately from uint v[0].y below as the original value is required -// to calculate the second uint32 of the digest for threshold comparison -const uint BLAKE2B_IV32_1 = 0x6A09E667u; - -// Used during G for vector bit rotations -const uvec4 ROTATE_1 = uvec4(1u); -const uvec4 ROTATE_8 = uvec4(8u); -const uvec4 ROTATE_16 = uvec4(16u); -const uvec4 ROTATE_24 = uvec4(24u); -const uvec4 ROTATE_31 = uvec4(31u); - -// Both buffers represent 16 uint64s as 32 uint32s -// because that's what GLSL offers, just like Javascript - -// Compression buffer, intialized to 2 instances of the initialization vector -// The following values have been modified from the BLAKE2B_IV: -// OUTLEN is constant 8 bytes -// v[0] ^= 0x01010000u ^ uint(OUTLEN); -// INLEN is constant 40 bytes: work value (8) + block hash (32) -// v[12] ^= uint(INLEN); -// It's always the "last" compression at this INLEN -// v[14] = ~v[14]; -const uvec2 blake2b_iv[16] = uvec2[16]( - uvec2(0xF2BDC900u, 0x6A09E667u), - uvec2(0x84CAA73Bu, 0xBB67AE85u), - uvec2(0xFE94F82Bu, 0x3C6EF372u), - uvec2(0x5F1D36F1u, 0xA54FF53Au), - uvec2(0xADE682D1u, 0x510E527Fu), - uvec2(0x2B3E6C1Fu, 0x9B05688Cu), - uvec2(0xFB41BD6Bu, 0x1F83D9ABu), - uvec2(0x137E2179u, 0x5BE0CD19u), - uvec2(0xF3BCC908u, 0x6A09E667u), - uvec2(0x84CAA73Bu, 0xBB67AE85u), - uvec2(0xFE94F82Bu, 0x3C6EF372u), - uvec2(0x5F1D36F1u, 0xA54FF53Au), - uvec2(0xADE682F9u, 0x510E527Fu), - uvec2(0x2B3E6C1Fu, 0x9B05688Cu), - uvec2(0x04BE4294u, 0xE07C2654u), - uvec2(0x137E2179u, 0x5BE0CD19u) -); - -// Iterated initialization vector -uvec2 v[16]; - -// Input data buffer -uvec2 m[16]; - -// G mixing function, compressing two subprocesses into one -void G ( - uint a0, uint b0, uint c0, uint d0, uvec2 x0, uvec2 y0, - uint a1, uint b1, uint c1, uint d1, uvec2 x1, uvec2 y1 -) { - uvec4 a = uvec4(v[a0], v[a1]); - uvec4 b = uvec4(v[b0], v[b1]); - uvec4 c = uvec4(v[c0], v[c1]); - uvec4 d = uvec4(v[d0], v[d1]); - uvec4 mx = uvec4(x0, x1); - uvec4 my = uvec4(y0, y1); - - a = a + b + uvec4(0u, uint(a.x + b.x < a.x), 0u, uint(a.z + b.z < a.z)); - a = a + mx + uvec4(0u, uint(a.x + mx.x < a.x), 0u, uint(a.z + mx.z < a.z)); - d = (d ^ a).yxwz; - c = c + d + uvec4(0u, uint(c.x + d.x < c.x), 0u, uint(c.z + d.z < c.z)); - b = ((b ^ c) >> ROTATE_24) | ((b ^ c) << ROTATE_8).yxwz; - a = a + b + uvec4(0u, uint(a.x + b.x < b.x), 0u, uint(a.z + b.z < b.z)); - a = a + my + uvec4(0u, uint(a.x + my.x < a.x), 0u, uint(a.z + my.z < a.z)); - d = ((d ^ a) >> ROTATE_16) | ((d ^ a) << ROTATE_16).yxwz; - c = c + d + uvec4(0u, uint(c.x + d.x < c.x), 0u, uint(c.z + d.z < c.z)); - b = ((b ^ c) >> ROTATE_31).yxwz | ((b ^ c) << ROTATE_1); - - v[a0] = a.xy; - v[b0] = b.xy; - v[c0] = c.xy; - v[d0] = d.xy; - v[a1] = a.zw; - v[b1] = b.zw; - v[c1] = c.zw; - v[d1] = d.zw; -} - -void main() { - // Initialize fragment output - nonce = uvec4(0u); - - // Nonce uniquely differentiated by pixel location - m[0u] = seed ^ uvec2(gl_FragCoord); - - // Block hash - m[1u] = uvec2(blockhash[0u], blockhash[1u]); - m[2u] = uvec2(blockhash[2u], blockhash[3u]); - m[3u] = uvec2(blockhash[4u], blockhash[5u]); - m[4u] = uvec2(blockhash[6u], blockhash[7u]); - - // Reset v - v = blake2b_iv; - - // Twelve rounds of G mixing - - // Round 0 - G(0u, 4u, 8u, 12u, m[0u], m[1u], 1u, 5u, 9u, 13u, m[2u], m[3u]); - G(2u, 6u, 10u, 14u, m[4u], m[5u], 3u, 7u, 11u, 15u, m[6u], m[7u]); - G(0u, 5u, 10u, 15u, m[8u], m[9u], 1u, 6u, 11u, 12u, m[10u], m[11u]); - G(2u, 7u, 8u, 13u, m[12u], m[13u], 3u, 4u, 9u, 14u, m[14u], m[15u]); - - // Round 1 - G(0u, 4u, 8u, 12u, m[14u], m[10u], 1u, 5u, 9u, 13u, m[4u], m[8u]); - G(2u, 6u, 10u, 14u, m[9u], m[15u], 3u, 7u, 11u, 15u, m[13u], m[6u]); - G(0u, 5u, 10u, 15u, m[1u], m[12u], 1u, 6u, 11u, 12u, m[0u], m[2u]); - G(2u, 7u, 8u, 13u, m[11u], m[7u], 3u, 4u, 9u, 14u, m[5u], m[3u]); - - // Round 2 - G(0u, 4u, 8u, 12u, m[11u], m[8u], 1u, 5u, 9u, 13u, m[12u], m[0u]); - G(2u, 6u, 10u, 14u, m[5u], m[2u], 3u, 7u, 11u, 15u, m[15u], m[13u]); - G(0u, 5u, 10u, 15u, m[10u], m[14u], 1u, 6u, 11u, 12u, m[3u], m[6u]); - G(2u, 7u, 8u, 13u, m[7u], m[1u], 3u, 4u, 9u, 14u, m[9u], m[4u]); - - // Round 3 - G(0u, 4u, 8u, 12u, m[7u], m[9u], 1u, 5u, 9u, 13u, m[3u], m[1u]); - G(2u, 6u, 10u, 14u, m[13u], m[12u], 3u, 7u, 11u, 15u, m[11u], m[14u]); - G(0u, 5u, 10u, 15u, m[2u], m[6u], 1u, 6u, 11u, 12u, m[5u], m[10u]); - G(2u, 7u, 8u, 13u, m[4u], m[0u], 3u, 4u, 9u, 14u, m[15u], m[8u]); - - // Round 4 - G(0u, 4u, 8u, 12u, m[9u], m[0u], 1u, 5u, 9u, 13u, m[5u], m[7u]); - G(2u, 6u, 10u, 14u, m[2u], m[4u], 3u, 7u, 11u, 15u, m[10u], m[15u]); - G(0u, 5u, 10u, 15u, m[14u], m[1u], 1u, 6u, 11u, 12u, m[11u], m[12u]); - G(2u, 7u, 8u, 13u, m[6u], m[8u], 3u, 4u, 9u, 14u, m[3u], m[13u]); - - // Round 5 - G(0u, 4u, 8u, 12u, m[2u], m[12u], 1u, 5u, 9u, 13u, m[6u], m[10u]); - G(2u, 6u, 10u, 14u, m[0u], m[11u], 3u, 7u, 11u, 15u, m[8u], m[3u]); - G(0u, 5u, 10u, 15u, m[4u], m[13u], 1u, 6u, 11u, 12u, m[7u], m[5u]); - G(2u, 7u, 8u, 13u, m[15u], m[14u], 3u, 4u, 9u, 14u, m[1u], m[9u]); - - // Round 6 - G(0u, 4u, 8u, 12u, m[12u], m[5u], 1u, 5u, 9u, 13u, m[1u], m[15u]); - G(2u, 6u, 10u, 14u, m[14u], m[13u], 3u, 7u, 11u, 15u, m[4u], m[10u]); - G(0u, 5u, 10u, 15u, m[0u], m[7u], 1u, 6u, 11u, 12u, m[6u], m[3u]); - G(2u, 7u, 8u, 13u, m[9u], m[2u], 3u, 4u, 9u, 14u, m[8u], m[11u]); - - // Round 7 - G(0u, 4u, 8u, 12u, m[13u], m[11u], 1u, 5u, 9u, 13u, m[7u], m[14u]); - G(2u, 6u, 10u, 14u, m[12u], m[1u], 3u, 7u, 11u, 15u, m[3u], m[9u]); - G(0u, 5u, 10u, 15u, m[5u], m[0u], 1u, 6u, 11u, 12u, m[15u], m[4u]); - G(2u, 7u, 8u, 13u, m[8u], m[6u], 3u, 4u, 9u, 14u, m[2u], m[10u]); - - // Round 8 - G(0u, 4u, 8u, 12u, m[6u], m[15u], 1u, 5u, 9u, 13u, m[14u], m[9u]); - G(2u, 6u, 10u, 14u, m[11u], m[3u], 3u, 7u, 11u, 15u, m[0u], m[8u]); - G(0u, 5u, 10u, 15u, m[12u], m[2u], 1u, 6u, 11u, 12u, m[13u], m[7u]); - G(2u, 7u, 8u, 13u, m[1u], m[4u], 3u, 4u, 9u, 14u, m[10u], m[5u]); - - // Round 9 - G(0u, 4u, 8u, 12u, m[10u], m[2u], 1u, 5u, 9u, 13u, m[8u], m[4u]); - G(2u, 6u, 10u, 14u, m[7u], m[6u], 3u, 7u, 11u, 15u, m[1u], m[5u]); - G(0u, 5u, 10u, 15u, m[15u], m[11u], 1u, 6u, 11u, 12u, m[9u], m[14u]); - G(2u, 7u, 8u, 13u, m[3u], m[12u], 3u, 4u, 9u, 14u, m[13u], m[0u]); - - // Round 10 - G(0u, 4u, 8u, 12u, m[0u], m[1u], 1u, 5u, 9u, 13u, m[2u], m[3u]); - G(2u, 6u, 10u, 14u, m[4u], m[5u], 3u, 7u, 11u, 15u, m[6u], m[7u]); - G(0u, 5u, 10u, 15u, m[8u], m[9u], 1u, 6u, 11u, 12u, m[10u], m[11u]); - G(2u, 7u, 8u, 13u, m[12u], m[13u], 3u, 4u, 9u, 14u, m[14u], m[15u]); - - // Round 11 - G(0u, 4u, 8u, 12u, m[14u], m[10u], 1u, 5u, 9u, 13u, m[4u], m[8u]); - G(2u, 6u, 10u, 14u, m[9u], m[15u], 3u, 7u, 11u, 15u, m[13u], m[6u]); - G(0u, 5u, 10u, 15u, m[1u], m[12u], 1u, 6u, 11u, 12u, m[0u], m[2u]); - G(2u, 7u, 8u, 13u, m[11u], m[7u], 3u, 4u, 9u, 14u, m[5u], m[3u]); - - // Pixel data set from work seed values - // Finalize digest from high bits, low bits can be safely ignored - if ((BLAKE2B_IV32_1 ^ v[0u].y ^ v[8u].y) >= threshold && (search || uvec2(gl_FragCoord) == uvec2(0u))) { - nonce = uvec4(1u, m[0u].y, m[0u].x, (uint(gl_FragCoord.x) << 16u) | uint(gl_FragCoord.y)); - } - - // Valid nonce not found - if (nonce.x == 0u) { - discard; - } -} -`; - -// src/shaders/gl-vertex.ts -var NanoPowGlVertexShader = `#version 300 es -#pragma vscode_glsllint_stage: vert -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif - -layout (location=0) in vec4 position; - -void main() { - gl_Position = position; -} -`; - -// src/classes/gl.ts -var NanoPowGl = class _NanoPowGl { - static #busy = false; - static #debug = false; - static #raf = 0; - /** Used to set canvas size. */ - static #cores = Math.max(1, Math.floor(navigator.hardwareConcurrency)); - static #WORKLOAD = 256 * this.#cores; - static #canvas = new OffscreenCanvas(this.#WORKLOAD, this.#WORKLOAD); - static get size() { - return this.#gl?.drawingBufferWidth; - } - static #gl; - static #drawProgram; - static #downsampleProgram; - static #vertexShader; - static #drawShader; - static #downsampleShader; - static #positionBuffer; - static #drawFbo; - static #downsampleFbos = []; - static #downsampleSrcLocation; - static #uboBuffer; - static #uboView = new DataView(new ArrayBuffer(144)); - static #seedBuffer; - static #seed = new BigUint64Array(1); - static #query; - static #pixels; - /**Vertex Positions, 2 triangles */ - static #positions = new Float32Array([ - -1, - -1, - 1, - -1, - 1, - 1, - -1, - 1 - ]); - /** Compile */ - static async init() { - if (this.#busy) return; - this.#busy = true; - try { - this.#canvas.addEventListener("webglcontextlost", (event) => { - event.preventDefault(); - console.warn("WebGL context lost. Waiting for it to be restored..."); - cancelAnimationFrame(this.#raf); - }, false); - this.#canvas.addEventListener("webglcontextrestored", (event) => { - console.warn("WebGL context restored. Reinitializing..."); - _NanoPowGl.init(); - }, false); - this.#gl = this.#canvas.getContext("webgl2"); - if (this.#gl == null) throw new Error("WebGL 2 is required"); - this.#drawProgram = this.#gl.createProgram(); - if (this.#drawProgram == null) throw new Error("Failed to create shader program"); - this.#vertexShader = this.#gl.createShader(this.#gl.VERTEX_SHADER); - if (this.#vertexShader == null) throw new Error("Failed to create vertex shader"); - this.#gl.shaderSource(this.#vertexShader, NanoPowGlVertexShader); - this.#gl.compileShader(this.#vertexShader); - if (!this.#gl.getShaderParameter(this.#vertexShader, this.#gl.COMPILE_STATUS)) - throw new Error(this.#gl.getShaderInfoLog(this.#vertexShader) ?? `Failed to compile vertex shader`); - this.#drawShader = this.#gl.createShader(this.#gl.FRAGMENT_SHADER); - if (this.#drawShader == null) throw new Error("Failed to create fragment shader"); - this.#gl.shaderSource(this.#drawShader, NanoPowGlDrawShader); - this.#gl.compileShader(this.#drawShader); - if (!this.#gl.getShaderParameter(this.#drawShader, this.#gl.COMPILE_STATUS)) - throw new Error(this.#gl.getShaderInfoLog(this.#drawShader) ?? `Failed to compile fragment shader`); - this.#gl.attachShader(this.#drawProgram, this.#vertexShader); - this.#gl.attachShader(this.#drawProgram, this.#drawShader); - this.#gl.linkProgram(this.#drawProgram); - if (!this.#gl.getProgramParameter(this.#drawProgram, this.#gl.LINK_STATUS)) - throw new Error(this.#gl.getProgramInfoLog(this.#drawProgram) ?? `Failed to link program`); - this.#downsampleProgram = this.#gl.createProgram(); - if (this.#downsampleProgram == null) throw new Error("Failed to create downsample program"); - this.#downsampleShader = this.#gl.createShader(this.#gl.FRAGMENT_SHADER); - if (this.#downsampleShader == null) throw new Error("Failed to create downsample shader"); - this.#gl.shaderSource(this.#downsampleShader, NanoPowGlDownsampleShader); - this.#gl.compileShader(this.#downsampleShader); - if (!this.#gl.getShaderParameter(this.#downsampleShader, this.#gl.COMPILE_STATUS)) - throw new Error(this.#gl.getShaderInfoLog(this.#downsampleShader) ?? `Failed to compile downsample shader`); - this.#gl.attachShader(this.#downsampleProgram, this.#vertexShader); - this.#gl.attachShader(this.#downsampleProgram, this.#downsampleShader); - this.#gl.linkProgram(this.#downsampleProgram); - if (!this.#gl.getProgramParameter(this.#downsampleProgram, this.#gl.LINK_STATUS)) - throw new Error(this.#gl.getProgramInfoLog(this.#downsampleProgram) ?? `Failed to link program`); - this.#gl.useProgram(this.#drawProgram); - const triangleArray = this.#gl.createVertexArray(); - this.#gl.bindVertexArray(triangleArray); - this.#positionBuffer = this.#gl.createBuffer(); - this.#gl.bindBuffer(this.#gl.ARRAY_BUFFER, this.#positionBuffer); - this.#gl.bufferData(this.#gl.ARRAY_BUFFER, this.#positions, this.#gl.STATIC_DRAW); - this.#gl.vertexAttribPointer(0, 2, this.#gl.FLOAT, false, 0, 0); - this.#gl.enableVertexAttribArray(0); - this.#gl.bindBuffer(this.#gl.ARRAY_BUFFER, null); - const texture = this.#gl.createTexture(); - this.#gl.bindTexture(this.#gl.TEXTURE_2D, texture); - this.#gl.texImage2D(this.#gl.TEXTURE_2D, 0, this.#gl.RGBA32UI, this.#gl.drawingBufferWidth, this.#gl.drawingBufferHeight, 0, this.#gl.RGBA_INTEGER, this.#gl.UNSIGNED_INT, null); - this.#gl.texParameteri(this.#gl.TEXTURE_2D, this.#gl.TEXTURE_MIN_FILTER, this.#gl.NEAREST); - this.#gl.texParameteri(this.#gl.TEXTURE_2D, this.#gl.TEXTURE_MAG_FILTER, this.#gl.NEAREST); - const framebuffer = this.#gl.createFramebuffer(); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, framebuffer); - this.#gl.framebufferTexture2D(this.#gl.FRAMEBUFFER, this.#gl.COLOR_ATTACHMENT0, this.#gl.TEXTURE_2D, texture, 0); - if (this.#gl.checkFramebufferStatus(this.#gl.FRAMEBUFFER) !== this.#gl.FRAMEBUFFER_COMPLETE) - throw new Error(`Failed to create drawing framebuffer`); - this.#drawFbo = { texture, framebuffer, size: { x: this.#gl.drawingBufferWidth, y: this.#gl.drawingBufferHeight } }; - for (let i = 1; i <= 4; i++) { - const width = this.#gl.drawingBufferWidth / 2 ** i; - const height = this.#gl.drawingBufferHeight / 2 ** i; - const texture2 = this.#gl.createTexture(); - this.#gl.bindTexture(this.#gl.TEXTURE_2D, texture2); - this.#gl.texImage2D(this.#gl.TEXTURE_2D, 0, this.#gl.RGBA32UI, width, height, 0, this.#gl.RGBA_INTEGER, this.#gl.UNSIGNED_INT, null); - this.#gl.texParameteri(this.#gl.TEXTURE_2D, this.#gl.TEXTURE_MIN_FILTER, this.#gl.NEAREST); - this.#gl.texParameteri(this.#gl.TEXTURE_2D, this.#gl.TEXTURE_MAG_FILTER, this.#gl.NEAREST); - const framebuffer2 = this.#gl.createFramebuffer(); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, framebuffer2); - this.#gl.framebufferTexture2D(this.#gl.FRAMEBUFFER, this.#gl.COLOR_ATTACHMENT0, this.#gl.TEXTURE_2D, texture2, 0); - if (this.#gl.checkFramebufferStatus(this.#gl.FRAMEBUFFER) !== this.#gl.FRAMEBUFFER_COMPLETE) - throw new Error(`Failed to create downsampling framebuffer ${i}`); - this.#downsampleFbos.push({ texture: texture2, framebuffer: framebuffer2, size: { x: width, y: height } }); - } - this.#downsampleSrcLocation = this.#gl.getUniformLocation(this.#downsampleProgram, "src"); - this.#gl.bindTexture(this.#gl.TEXTURE_2D, null); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, null); - this.#uboBuffer = this.#gl.createBuffer(); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#uboBuffer); - this.#gl.bufferData(this.#gl.UNIFORM_BUFFER, 144, this.#gl.DYNAMIC_DRAW); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - this.#gl.bindBufferBase(this.#gl.UNIFORM_BUFFER, 0, this.#uboBuffer); - this.#gl.uniformBlockBinding(this.#drawProgram, this.#gl.getUniformBlockIndex(this.#drawProgram, "UBO"), 0); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - this.#seedBuffer = this.#gl.createBuffer(); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#seedBuffer); - this.#gl.bufferData(this.#gl.UNIFORM_BUFFER, 16, this.#gl.DYNAMIC_DRAW); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - this.#gl.bindBufferBase(this.#gl.UNIFORM_BUFFER, 1, this.#seedBuffer); - this.#gl.uniformBlockBinding(this.#drawProgram, this.#gl.getUniformBlockIndex(this.#drawProgram, "WORK"), 1); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - this.#query = this.#gl.createQuery(); - this.#pixels = new Uint32Array(this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight * 4); - console.log(`NanoPow WebGL initialized at ${this.#gl.drawingBufferWidth}x${this.#gl.drawingBufferHeight}. Maximum nonces checked per frame: ${this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight}`); - } catch (err) { - throw new Error("WebGL initialization failed.", { cause: err }); - } finally { - this.#busy = false; - } - } - static reset() { - cancelAnimationFrame(_NanoPowGl.#raf); - _NanoPowGl.#gl?.deleteQuery(_NanoPowGl.#query); - _NanoPowGl.#query = null; - _NanoPowGl.#gl?.deleteBuffer(_NanoPowGl.#seedBuffer); - _NanoPowGl.#seedBuffer = null; - _NanoPowGl.#gl?.deleteBuffer(_NanoPowGl.#uboBuffer); - _NanoPowGl.#uboBuffer = null; - for (const fbo of _NanoPowGl.#downsampleFbos) { - _NanoPowGl.#gl?.deleteFramebuffer(fbo.framebuffer); - _NanoPowGl.#gl?.deleteTexture(fbo.texture); - } - _NanoPowGl.#downsampleFbos = []; - _NanoPowGl.#gl?.deleteShader(_NanoPowGl.#downsampleShader); - _NanoPowGl.#downsampleShader = null; - _NanoPowGl.#gl?.deleteProgram(_NanoPowGl.#downsampleProgram); - _NanoPowGl.#downsampleProgram = null; - _NanoPowGl.#gl?.deleteFramebuffer(_NanoPowGl.#drawFbo?.framebuffer ?? null); - _NanoPowGl.#drawFbo = null; - _NanoPowGl.#gl?.deleteTexture(_NanoPowGl.#drawFbo); - _NanoPowGl.#drawFbo = null; - _NanoPowGl.#gl?.deleteBuffer(_NanoPowGl.#positionBuffer); - _NanoPowGl.#positionBuffer = null; - _NanoPowGl.#gl?.deleteShader(_NanoPowGl.#drawShader); - _NanoPowGl.#drawShader = null; - _NanoPowGl.#gl?.deleteShader(_NanoPowGl.#vertexShader); - _NanoPowGl.#vertexShader = null; - _NanoPowGl.#gl?.deleteProgram(_NanoPowGl.#drawProgram); - _NanoPowGl.#drawProgram = null; - _NanoPowGl.#gl = null; - _NanoPowGl.#busy = false; - _NanoPowGl.init(); - } - static #logAverages(times) { - let count = times.length, sum = 0, reciprocals = 0, logarithms = 0, truncated = 0, min = 65535, max = 0, rate = 0; - times.sort(); - for (let i = 0; i < count; i++) { - sum += times[i]; - reciprocals += 1 / times[i]; - logarithms += Math.log(times[i]); - min = Math.min(min, times[i]); - max = Math.max(max, times[i]); - if (count < 3 || i > count * 0.1 && i < count * 0.9) truncated += times[i]; - } - const averages = { - "Count (frames)": count, - "Total (ms)": sum, - "Rate (f/s)": 1e3 * count * 0.8 / (truncated || sum), - "Minimum (ms)": min, - "Maximum (ms)": max, - "Arithmetic Mean (ms)": sum / count, - "Truncated Mean (ms)": truncated / count, - "Harmonic Mean (ms)": count / reciprocals, - "Geometric Mean (ms)": Math.exp(logarithms / count) - }; - console.log(`Averages: ${JSON.stringify(averages)}`); - console.table(averages); - } - static #draw(seed) { - if (this.#gl == null || this.#query == null) throw new Error("WebGL 2 is required to draw and query pixels"); - if (this.#drawFbo == null) throw new Error("FBO is required to draw"); - if (this.#seed[0] == null || this.#seedBuffer == null) throw new Error("Seed is required to draw"); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#seedBuffer); - this.#gl.bufferSubData(this.#gl.UNIFORM_BUFFER, 0, seed); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - this.#gl.useProgram(this.#drawProgram); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, this.#drawFbo.framebuffer); - this.#gl.activeTexture(this.#gl.TEXTURE0); - this.#gl.bindTexture(this.#gl.TEXTURE_2D, this.#drawFbo.texture); - this.#gl.beginQuery(this.#gl.ANY_SAMPLES_PASSED_CONSERVATIVE, this.#query); - this.#gl.viewport(0, 0, this.#drawFbo.size.x, this.#drawFbo.size.y); - this.#gl.drawArrays(this.#gl.TRIANGLES, 0, 4); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, null); - this.#gl.endQuery(this.#gl.ANY_SAMPLES_PASSED_CONSERVATIVE); - } - static async #checkQueryResult() { - return new Promise((resolve, reject) => { - function check() { - try { - if (_NanoPowGl.#gl == null || _NanoPowGl.#query == null) throw new Error("WebGL 2 is required to check query results"); - if (_NanoPowGl.#gl.getQueryParameter(_NanoPowGl.#query, _NanoPowGl.#gl.QUERY_RESULT_AVAILABLE)) { - resolve(!!_NanoPowGl.#gl.getQueryParameter(_NanoPowGl.#query, _NanoPowGl.#gl.QUERY_RESULT)); - } else { - _NanoPowGl.#raf = requestAnimationFrame(check); - } - } catch (err) { - reject(err); - } - } - check(); - }); - } - /** - * 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 workHex - Original nonce if provided for a validation call - * @returns Nonce as an 8-byte (16-char) hexadecimal string - */ - static #readResult(workHex) { - if (this.#gl == null) throw new Error("WebGL 2 is required to read pixels"); - if (this.#drawFbo == null) throw new Error("Source FBO is required to downsample"); - let source = this.#drawFbo; - let pixelCount; - const start = performance.now(); - if (workHex != null) { - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, source.framebuffer); - this.#gl.readPixels(0, 0, 1, 1, this.#gl.RGBA_INTEGER, this.#gl.UNSIGNED_INT, this.#pixels); - pixelCount = 4; - } else { - this.#gl.useProgram(this.#downsampleProgram); - for (const fbo of this.#downsampleFbos) { - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, fbo.framebuffer); - this.#gl.activeTexture(this.#gl.TEXTURE0); - this.#gl.bindTexture(this.#gl.TEXTURE_2D, source.texture); - this.#gl.uniform1i(this.#downsampleSrcLocation, 0); - this.#gl.viewport(0, 0, fbo.size.x, fbo.size.y); - this.#gl.drawArrays(this.#gl.TRIANGLES, 0, 4); - source = fbo; - } - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, source.framebuffer); - this.#gl.readPixels(0, 0, source.size.x, source.size.y, this.#gl.RGBA_INTEGER, this.#gl.UNSIGNED_INT, this.#pixels); - pixelCount = source.size.x * source.size.y * 4; - } - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, null); - for (let i = 0; i < pixelCount; i += 4) { - if (this.#pixels[i] !== 0) { - if (this.#debug) console.log(`readResults (${performance.now() - start} ms)`); - if (this.#debug) console.log(`Pixel: rgba(${this.#pixels[i]}, ${this.#pixels[i + 1]}, ${this.#pixels[i + 2]}, ${this.#pixels[i + 3].toString(16).padStart(8, "0")})`); - const hex = `${this.#pixels[i + 1].toString(16).padStart(8, "0")}${this.#pixels[i + 2].toString(16).padStart(8, "0")}`; - if (workHex == null || workHex == hex) return hex; - } - } - throw new Error("Query reported result but nonce value not found"); - } - /** - * 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, options) { - if (this.#busy) { - console.log("NanoPowGl is busy. Retrying search..."); - return new Promise((resolve) => { - setTimeout(async () => { - const result = this.search(hash, options); - resolve(result); - }, 100); - }); - } - this.#busy = true; - if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new Error(`Invalid hash ${hash}`); - const threshold = typeof options?.threshold !== "number" || options.threshold < 0 || options.threshold > 4294967295 ? 4294967288 : options.threshold; - const effort = typeof options?.effort !== "number" || options.effort < 1 || options.effort > 32 ? this.#cores : options.effort; - this.#debug = !!options?.debug; - if (this.#WORKLOAD !== 256 * effort) { - this.#WORKLOAD = 256 * effort; - this.#canvas.height = this.#WORKLOAD; - this.#canvas.width = this.#WORKLOAD; - this.reset(); - } - if (_NanoPowGl.#gl == null) throw new Error("WebGL 2 is required"); - if (this.#gl == null) throw new Error("WebGL 2 is required"); - if (this.#drawFbo == null) throw new Error("WebGL framebuffer is required"); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, this.#drawFbo.framebuffer); - this.#gl.clearBufferuiv(this.#gl.COLOR, 0, [0, 0, 0, 0]); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, null); - for (let i = 0; i < this.#uboView.byteLength; i++) this.#uboView.setUint8(i, 0); - for (let i = 0; i < 64; i += 8) { - const uint32 = hash.slice(i, i + 8); - this.#uboView.setUint32(i * 2, parseInt(uint32, 16)); - } - this.#uboView.setUint32(128, threshold, true); - this.#uboView.setUint32(132, 1, true); - if (this.#debug) console.log("UBO", this.#uboView.buffer.slice(0)); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#uboBuffer); - this.#gl.bufferSubData(this.#gl.UNIFORM_BUFFER, 0, this.#uboView); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - let times = []; - let start = performance.now(); - let nonce = null; - if (this.#debug) console.groupCollapsed("Seeds (click to view)"); - while (nonce == null) { - start = performance.now(); - const random0 = Math.floor(Math.random() * 4294967295); - const random1 = Math.floor(Math.random() * 4294967295); - this.#seed[0] = BigInt(random0) << 32n | BigInt(random1); - if (this.#debug) console.log("Seed", this.#seed); - this.#draw(this.#seed); - const found = await this.#checkQueryResult(); - times.push(performance.now() - start); - if (found) { - if (this.#debug) console.groupEnd(); - nonce = this.#readResult(); - } - } - this.#busy = false; - if (this.#debug) this.#logAverages(times); - return nonce; - } - /** - * Validates that a nonce satisfies Nano proof-of-work requirements. - * - * @param {string} work - Hexadecimal proof-of-work value to validate - * @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 validate(work, hash, options) { - if (this.#busy) { - console.log("NanoPowGl is busy. Retrying validate..."); - return new Promise((resolve) => { - setTimeout(async () => { - const result = this.validate(work, hash, options); - resolve(result); - }, 100); - }); - } - this.#busy = true; - if (!/^[A-Fa-f0-9]{16}$/.test(work)) throw new Error(`Invalid work ${work}`); - if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new Error(`Invalid hash ${hash}`); - const threshold = typeof options?.threshold !== "number" || options.threshold < 0 || options.threshold > 4294967295 ? 4294967288 : options.threshold; - this.#debug = !!options?.debug; - if (_NanoPowGl.#gl == null) throw new Error("WebGL 2 is required"); - if (this.#gl == null) throw new Error("WebGL 2 is required"); - if (this.#drawFbo == null) throw new Error("WebGL framebuffer is required"); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, this.#drawFbo.framebuffer); - this.#gl.clearBufferuiv(this.#gl.COLOR, 0, [0, 0, 0, 0]); - this.#gl.bindFramebuffer(this.#gl.FRAMEBUFFER, null); - for (let i = 0; i < this.#uboView.byteLength; i++) this.#uboView.setUint8(i, 0); - for (let i = 0; i < 64; i += 8) { - const uint32 = hash.slice(i, i + 8); - this.#uboView.setUint32(i * 2, parseInt(uint32, 16)); - } - this.#uboView.setUint32(128, threshold, true); - this.#uboView.setUint32(132, 0, true); - if (this.#debug) console.log("UBO", this.#uboView.buffer.slice(0)); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, this.#uboBuffer); - this.#gl.bufferSubData(this.#gl.UNIFORM_BUFFER, 0, this.#uboView); - this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null); - let nonce = null; - this.#seed[0] = BigInt(`0x${work}`); - if (this.#debug) console.log("Work", this.#seed); - this.#draw(this.#seed); - let found = await this.#checkQueryResult(); - if (found) { - try { - nonce = this.#readResult(work); - } catch (err) { - found = false; - } - } - this.#busy = false; - if (found && nonce !== work) throw new Error(`Nonce found but does not match work`); - return found; - } -}; - -// src/classes/gpu.ts -var NanoPowGpu = class _NanoPowGpu { - // Initialize WebGPU - static #busy = false; - static #debug = false; - static #device = null; - static #gpuBufferReset = new BigUint64Array([0n, 0n]); - static #gpuBuffer; - static #cpuBuffer; - static #uboBuffer; - static #uboView; - static #bindGroupLayout; - static #searchPipeline; - static #validatePipeline; - // Initialize WebGPU - static async init() { - if (this.#busy) return; - this.#busy = true; - try { - if (navigator.gpu == null) throw new Error("WebGPU is not supported in this browser."); - const adapter = await navigator.gpu.requestAdapter(); - if (adapter == null) throw new Error("WebGPU adapter refused by browser."); - const device = await adapter.requestDevice(); - if (!(device instanceof GPUDevice)) throw new Error("WebGPU device failed to load."); - device.lost.then(this.reset); - this.#device = device; - this.setup(); - } catch (err) { - throw new Error("WebGPU initialization failed.", { cause: err }); - } finally { - this.#busy = false; - } - } - static setup() { - if (this.#device == null) throw new Error(`WebGPU device failed to load.`); - this.#gpuBuffer = this.#device.createBuffer({ - size: 16, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC - }); - this.#cpuBuffer = this.#device.createBuffer({ - size: 16, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ - }); - this.#uboBuffer = this.#device.createBuffer({ - size: 48, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST - }); - this.#uboView = new DataView(new ArrayBuffer(48)); - this.#bindGroupLayout = this.#device.createBindGroupLayout({ - entries: [ - { - binding: 0, - visibility: GPUShaderStage.COMPUTE, - buffer: { type: "uniform" } - }, - { - binding: 1, - visibility: GPUShaderStage.COMPUTE, - buffer: { type: "storage" } - } - ] - }); - const shaderModule = this.#device.createShaderModule({ - code: compute_default - }); - this.#searchPipeline = this.#device.createComputePipeline({ - layout: this.#device.createPipelineLayout({ - bindGroupLayouts: [this.#bindGroupLayout] - }), - compute: { - entryPoint: "search", - module: shaderModule - } - }); - this.#validatePipeline = this.#device.createComputePipeline({ - layout: this.#device.createPipelineLayout({ - bindGroupLayouts: [this.#bindGroupLayout] - }), - compute: { - entryPoint: "validate", - module: shaderModule - } - }); - console.log(`NanoPow WebGPU initialized. Recommended effort: ${Math.max(1, Math.floor(navigator.hardwareConcurrency / 2))}`); - } - static reset() { - console.warn(`GPU device lost. Reinitializing...`); - _NanoPowGpu.#cpuBuffer?.destroy(); - _NanoPowGpu.#gpuBuffer?.destroy(); - _NanoPowGpu.#uboBuffer?.destroy(); - _NanoPowGpu.#busy = false; - _NanoPowGpu.init(); - } - static #logAverages(times) { - let count = times.length, truncatedCount = 0, truncated = 0, sum = 0, reciprocals = 0, logarithms = 0, min = Number.MAX_SAFE_INTEGER, max = 0, median = 0, rate = 0; - times.sort(); - for (let i = 0; i < count; i++) { - sum += times[i]; - reciprocals += 1 / times[i]; - logarithms += Math.log(times[i]); - min = Math.min(min, times[i]); - max = Math.max(max, times[i]); - if (i === Math.ceil(count / 2)) { - median = times[i]; - } - if (count < 3 || i > 0.1 * count && i < 0.9 * (count - 1)) { - truncated += times[i]; - truncatedCount++; - } - } - const averages = { - "Count (dispatches)": count, - "Total (ms)": sum, - "Rate (d/s)": 1e3 * truncatedCount / (truncated || sum), - "Minimum (ms)": min, - "Maximum (ms)": max, - "Median (ms)": median, - "Arithmetic Mean (ms)": sum / count, - "Truncated Mean (ms)": truncated / truncatedCount, - "Harmonic Mean (ms)": count / reciprocals, - "Geometric Mean (ms)": Math.exp(logarithms / count) - }; - console.table(averages); - } - static async #dispatch(pipeline, seed, hash, threshold, passes) { - if (this.#device == null) throw new Error(`WebGPU device failed to load.`); - for (let i = 0; i < this.#uboView.byteLength; i++) this.#uboView.setUint8(i, 0); - for (let i = 0; i < 64; i += 16) { - const u64 = hash.slice(i, i + 16); - this.#uboView.setBigUint64(i / 2, BigInt(`0x${u64}`)); - } - this.#uboView.setBigUint64(32, seed, true); - this.#uboView.setUint32(40, threshold, true); - if (this.#debug) console.log("UBO", this.#uboView); - this.#device.queue.writeBuffer(this.#uboBuffer, 0, this.#uboView); - this.#device.queue.writeBuffer(this.#gpuBuffer, 0, this.#gpuBufferReset); - const bindGroup = this.#device.createBindGroup({ - layout: this.#bindGroupLayout, - entries: [ - { - binding: 0, - resource: { - buffer: this.#uboBuffer - } - }, - { - binding: 1, - resource: { - buffer: this.#gpuBuffer - } - } - ] - }); - const commandEncoder = this.#device.createCommandEncoder(); - const passEncoder = commandEncoder.beginComputePass(); - passEncoder.setPipeline(pipeline); - passEncoder.setBindGroup(0, bindGroup); - passEncoder.dispatchWorkgroups(passes, passes); - passEncoder.end(); - commandEncoder.copyBufferToBuffer(this.#gpuBuffer, 0, this.#cpuBuffer, 0, 12); - this.#device.queue.submit([commandEncoder.finish()]); - let data = null; - try { - await this.#cpuBuffer.mapAsync(GPUMapMode.READ); - await this.#device.queue.onSubmittedWorkDone(); - data = new DataView(this.#cpuBuffer.getMappedRange().slice(0)); - this.#cpuBuffer.unmap(); - } catch (err) { - console.warn(`Error getting data from GPU. ${err}`); - return this.#dispatch(pipeline, seed, hash, threshold, passes); - } - if (this.#debug) console.log("gpuBuffer data", data); - if (data == null) throw new Error(`Failed to get data from buffer.`); - return data; - } - /** - * 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 {NanoPowOptions} options - Used to configure search execution - */ - static async search(hash, options) { - 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 () => { - const result = this.search(hash, options); - resolve(result); - }, 100); - }); - } - this.#busy = true; - const threshold = typeof options?.threshold !== "number" || options.threshold < 0 || options.threshold > 4294967295 ? 4294967288 : options.threshold; - const effort = typeof options?.effort !== "number" || options.effort < 1 || options.effort > 32 ? 2048 : options.effort * 256; - this.#debug = !!options?.debug; - 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.`); - } - let times = []; - let start = performance.now(); - let nonce = 0n; - do { - start = performance.now(); - const random0 = Math.floor(Math.random() * 4294967295); - const random1 = Math.floor(Math.random() * 4294967295); - const seed = BigInt(random0) << 32n | BigInt(random1); - if (this.#debug) console.log(`seed: ${seed}`); - const data = await this.#dispatch(this.#searchPipeline, seed, hash, threshold, effort); - nonce = data.getBigUint64(0, true); - this.#busy = !data.getUint32(8); - times.push(performance.now() - start); - } while (this.#busy); - if (this.#debug) this.#logAverages(times); - return nonce.toString(16).padStart(16, "0"); - } - /** - * Validates that a nonce satisfies Nano proof-of-work requirements. - * - * @param {string} work - Hexadecimal proof-of-work value to validate - * @param {string} hash - Hexadecimal hash of previous block, or public key for new accounts - * @param {NanoPowOptions} options - Options used to configure search execution - */ - static async validate(work, hash, options) { - 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 () => { - const result = this.validate(work, hash, options); - resolve(result); - }, 100); - }); - } - this.#busy = true; - this.#debug = !!options?.debug; - const threshold = typeof options?.threshold !== "number" || options.threshold < 0 || options.threshold > 4294967295 ? 4294967288 : options.threshold; - 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 seed = BigInt(`0x${work}`); - if (this.#debug) console.log(`work: ${work}`); - const data = await this.#dispatch(this.#validatePipeline, seed, hash, threshold, 1); - const nonce = data.getBigUint64(0, true).toString(16).padStart(16, "0"); - if (this.#debug) console.log(`nonce: ${nonce}`); - const found = !!data.getUint32(8); - this.#busy = false; - if (found && work !== nonce) throw new Error(`Nonce (${nonce}) found but does not match work (${work})`); - return found; - } -}; - -// src/classes/index.ts -var isGlSupported; -var isGpuSupported = false; -try { - await NanoPowGpu.init(); - isGpuSupported = true; -} catch (err) { - console.warn("WebGPU is not supported in this environment.\n", err); - isGpuSupported = false; -} -try { - await NanoPowGl.init(); - isGlSupported = true; -} catch (err) { - console.warn("WebGL is not supported in this environment.\n", err); - isGlSupported = false; -} -var NanoPow = isGpuSupported ? NanoPowGpu : isGlSupported ? NanoPowGl : null; - -// src/main.ts -var main_default = NanoPow; -export { - NanoPow, - NanoPowGl, - NanoPowGpu, - main_default as default -}; diff --git a/src/global.ts b/src/global.ts deleted file mode 100644 index b3d4d1c..0000000 --- a/src/global.ts +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Chris Duncan -// SPDX-License-Identifier: GPL-3.0-or-later - -import { NanoPow as np, NanoPowGl as gl, NanoPowGpu as gpu} from './main.js'; -(globalThis as any).NanoPow ??= np; -(globalThis as any).NanoPowGl ??= gl; -(globalThis as any).NanoPowGpu ??= gpu;