// SPDX-License-Identifier: GPL-3.0-or-later AND MIT
import { NanoPowGlFragmentShader, NanoPowGlVertexShader } from '../shaders'
+import type { NanoPowOptions } from '../../types.d.ts'
export class NanoPowGl {
/** Used to set canvas size. Must be a multiple of 256. */
* @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> {
+ static async search (hash: string, options?: NanoPowOptions): Promise<string> {
if (NanoPowGl.#gl == null) throw new Error('WebGL 2 is required')
- if (!/^[A-Fa-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')
+ if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new Error(`Invalid hash ${hash}`)
+ const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
+ ? 0xfffffff8
+ : options.threshold
+ const effort = (typeof options?.effort !== 'number' || options.effort < 0x1 || options.effort > 0x10)
+ ? 0x8
+ : options.effort
+ const debug = !!(options?.debug)
/** Set up uniform buffer object */
const uboView = new DataView(new ArrayBuffer(144))
uboView.setUint32(i * 2, parseInt(uint32, 16))
}
uboView.setUint32(128, threshold, true)
- uboView.setFloat32(132, NanoPowGl.#WORKLOAD - 1, true)
+ uboView.setFloat32(132, 256 * effort, true)
NanoPowGl.#gl.bindBuffer(NanoPowGl.#gl.UNIFORM_BUFFER, NanoPowGl.#uboBuffer)
NanoPowGl.#gl.bufferSubData(NanoPowGl.#gl.UNIFORM_BUFFER, 0, uboView)
NanoPowGl.#gl.bindBuffer(NanoPowGl.#gl.UNIFORM_BUFFER, null)
* @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: string, hash: string, threshold: number = 0xfffffff8): Promise<boolean> {
+ static async validate (work: string, hash: string, options?: NanoPowOptions): Promise<boolean> {
if (NanoPowGl.#gl == null) throw new Error('WebGL 2 is required')
+ if (this.#gl == null) throw new Error('WebGL 2 is required')
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}`)
- if (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`)
- if (this.#gl == null) throw new Error('WebGL 2 is required')
+ const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
+ ? 0xfffffff8
+ : options.threshold
+ const debug = !!(options?.debug)
/** Set up uniform buffer object */
const uboView = new DataView(new ArrayBuffer(144))
uboView.setUint32(i * 2, parseInt(uint32, 16))
}
uboView.setUint32(128, threshold, true)
- uboView.setFloat32(132, NanoPowGl.#WORKLOAD - 1, true)
+ uboView.setFloat32(132, 1, true)
NanoPowGl.#gl.bindBuffer(NanoPowGl.#gl.UNIFORM_BUFFER, NanoPowGl.#uboBuffer)
NanoPowGl.#gl.bufferSubData(NanoPowGl.#gl.UNIFORM_BUFFER, 0, uboView)
NanoPowGl.#gl.bindBuffer(NanoPowGl.#gl.UNIFORM_BUFFER, null)
/// <reference types="@webgpu/types" />
import { NanoPowGpuComputeShader } from '../shaders'
-
+import type { NanoPowOptions } from '../../types.d.ts'
/**
* Nano proof-of-work using WebGPU.
*/
NanoPowGpu.init()
}
+ static logAverages (times: number[]): void {
+ let count = times.length, sum = 0, reciprocals = 0, logarithms = 0, truncated = 0, min = 0xffff, 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 > 2 && i > (count * 0.1) && i < (count * 0.9)) truncated += times[i]
+ }
+ const averages = {
+ Count: `${count} dispatches`,
+ Total: `${sum} ms`,
+ Rate: `${count / sum * 1000} dispatches/second`,
+ Minimum: `${min} ms`,
+ Maximum: `${max} ms`,
+ Arithmetic: `${sum / count} ms`,
+ Truncated: `${truncated / count} ms`,
+ Harmonic: `${count / reciprocals} ms`,
+ Geometric: `${Math.exp(logarithms / count)} ms`
+ }
+ console.table(averages)
+ }
+
static async #dispatch (seed: bigint, hash: string, threshold: number, passes: number): Promise<DataView> {
if (this.#device == null) throw new Error(`WebGPU device failed to load.`)
// Set up uniform buffer object
* 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
+ * @param {NanoPowOptions} options - Used to configure search execution
*/
- static async search (hash: string, threshold: number = 0xfffffff8): Promise<string> {
+ static async search (hash: string, options?: NanoPowOptions): Promise<string> {
if (this.#busy) {
return new Promise(resolve => {
- setTimeout(async () => {
- const result = this.search(hash, threshold)
+ setTimeout(async (): Promise<void> => {
+ const result = this.search(hash, options)
resolve(result)
}, 100)
})
}
this.#busy = true
if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`)
- if (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`)
+ const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
+ ? 0xfffffff8
+ : options.threshold
+ const effort = (typeof options?.effort !== 'number' || options.effort < 0x1 || options.effort > 0x10)
+ ? 0x8
+ : options.effort
+ const debug = !!(options?.debug)
// Ensure WebGPU is initialized before calculating
let loads = 0
}
if (this.#device == null) throw new Error(`WebGPU device failed to load.`)
+ let times = []
+ let start = performance.now()
let nonce = 0n
do {
+ start = performance.now()
const random = Math.floor(Math.random() * 0xffffffff)
const seed = (BigInt(random) << 32n) | BigInt(random)
- const data = await this.#dispatch(seed, hash, threshold, 0x400)
+ const data = await this.#dispatch(seed, hash, threshold, effort * 0x100)
nonce = data.getBigUint64(0, true)
this.#busy = !data.getUint32(8)
+ times.push(performance.now() - start)
} while (this.#busy)
+ if (debug) this.logAverages(times)
return nonce.toString(16).padStart(16, '0')
}
*
* @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
+ * @param {NanoPowOptions} options - Options used to configure search execution
*/
- static async validate (work: string, hash: string, threshold: number = 0xfffffff8): Promise<boolean> {
+ static async validate (work: string, hash: string, options?: NanoPowOptions): Promise<boolean> {
if (this.#busy) {
return new Promise(resolve => {
- setTimeout(async () => {
- const result = this.validate(work, hash, threshold)
+ setTimeout(async (): Promise<void> => {
+ const result = this.validate(work, hash, options)
resolve(result)
}, 100)
})
this.#busy = true
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 (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`)
+ const debug = !!(options?.debug)
+ const threshold = (typeof options?.threshold !== 'number' || options.threshold < 0x0 || options.threshold > 0xffffffff)
+ ? 0xfffffff8
+ : options.threshold
// Ensure WebGPU is initialized before calculating
let loads = 0
* this 8-byte value is then XOR'd with a different dimensional index from
* the thread identifier.
*/
-@compute @workgroup_size(256)
+@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
if (atomicLoad(&work.found) != 0u) { return; }
/**
* Twelve rounds of G mixing as part of BLAKE2b compression step.
-*
+ *
* Each sigma r index correlates with the reference implementation, but each
* sigma i index is doubled due to using two u32 array elements to represent
* one uint64_t.
console.log(`Geometric Mean: ${geometric} ms`)
}
- export async function run (COUNT) {
+ export async function run (size, effort, gl, debug) {
+ if (gl) NanoPow = NanoPowGl
document.getElementById('status').innerHTML = `TESTING IN PROGRESS`
console.log(`%cNanoPow`, 'color:green', 'Checking validate()')
const expectFalse = await NanoPow.validate('0000000000000000', '0000000000000000000000000000000000000000000000000000000000000000')
return
}
- console.log(`%cNanoPow (${type})`, 'color:green', `Calculate proof-of-work for ${COUNT} unique send block hashes`)
+ console.log(`%cNanoPow (${type})`, 'color:green', `Calculate proof-of-work for ${size} unique send block hashes`)
const times = []
- document.getElementById('output').innerHTML += `Now testing: NanoPow (${type})<br/>`
- for (let i = 0; i < COUNT; i++) {
+ document.getElementById('output').innerHTML += `Now testing: NanoPow (${type}) | Dispatch: ${(effort * 0x100) ** 2} | Threads/Dispatch: ${64 * ((effort * 0x100) ** 2)}<br/>`
+ for (let i = 0; i < size; i++) {
const hash = random()
let work = null
let isValid = null
const start = performance.now()
try {
- work = await NanoPow.search(hash)
+ work = await NanoPow.search(hash, { effort, debug })
isValid = (await NanoPow.validate(work, hash)) ? 'VALID' : 'INVALID'
} catch (err) {
document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
}
document.getElementById('go').addEventListener('click', e => {
- const count = document.getElementById('count')
- run(count.value)
+ const size = document.getElementById('size')
+ const effort = document.getElementById('effort')
+ const gl = document.getElementById('gl')
+ const debug = document.getElementById('debug')
+ run(size.value, effort.value, gl.checked, debug.checked)
})
</script>
- <style>body{background:black;color:white;}a{color:darkcyan;}</style>
+ <style>body{background:black;color:white;}a{color:darkcyan;}input[type=number]{width:5em;}</style>
</head>
<body>
<p>NanoPow uses cutting edge WebGPU technology. Not all browsers are supported.</p>
<p>NanoPow uses WebGL 2.0 as a fallback option if WebGPU is not detected.</p>
<p>Times below are in milliseconds and summarized by various averaging methods.</p>
+ <p>Level of Effort depends on hardware and does not guarantee faster results.</p>
<hr />
- <input id="count" type="number" value="2" />
+ <label for="size">Test Size</label>
+ <input id="size" type="number" value="64" />
+ <label for="effort">Effort (1-16)</label>
+ <input id="effort" type="number" value="8" />
+ <label for="gl">Force WebGL?</label>
+ <input id="gl" type="checkbox" />
+ <label for="debug">Debug?</label>
+ <input id="debug" type="checkbox" />
<button id="go">Go</button>
<h3 id="status">WAITING</h3>
<hr />
declare const NanoPow: typeof NanoPowGl | typeof NanoPowGpu | null
+/**
+* Used to configure NanoPow.
+*
+* @param {boolean} [debug=false] - Enables additional debug logging to the console. Default: false
+* @param {number} [effort=0x8] - Multiplier for dispatching work search. Larger values are not necessarily better since they can quickly overwhelm the GPU. Ignored when validating. Default: 0x8
+* @param {number} [threshold=0xfffffff8] - Minimum value result of `BLAKE2b(nonce||blockhash) << 0x32`. Default: 0xFFFFFFF8
+*/
+export type NanoPowOptions = {
+ debug?: boolean
+ effort?: 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xa | 0xb | 0xc | 0xd | 0xe | 0xf | 0x10
+ threshold?: number
+}
+
export declare class NanoPowGl {
#private
/** Compile */