static #isInitialized: boolean = false
static #busy: boolean = false
static #debug: boolean = false
+ static #queue: any[] = []
static #device: GPUDevice | null = null
static #bufferReset: BigUint64Array = new BigUint64Array([0n, 0n, 0n, 0n])
static #gpuBuffer: GPUBuffer
// Initialize WebGPU
static async init (): Promise<void> {
console.log('Initializing NanoPowGpu.')
- if (this.#busy) return
- this.#busy = true
// Request device and adapter
try {
if (navigator.gpu == null) throw new Error('WebGPU is not supported in this browser.')
device.lost?.then(this.reset)
this.#device = device
await this.setup()
+ this.#isInitialized = true
} catch (err) {
throw new Error('WebGPU initialization failed.', { cause: err })
- } finally {
- this.#isInitialized = true
- this.#busy = false
}
}
* Validate options and normalize its properties.
*/
static async #work_init (work: string | null, hash: string, options?: NanoPowOptions) {
- if (this.#busy) {
- console.log('NanoPowGpu is busy. Retrying search...')
- return new Promise(r => {
- setTimeout(async () => {
- r(this.#work_init(work, hash, options))
- }, 100)
- }) as Promise<{ difficulty: any, effort: any }>
- }
- if (this.#isInitialized === false) this.init()
- this.#busy = true
-
+ if (this.#isInitialized === false) await this.init()
if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`)
if (work != null && !/^[A-Fa-f0-9]{16}$/.test(work)) throw new TypeError(`Invalid work ${work}`)
options ??= {}
if (this.#resultView == null) throw new Error(`Failed to get data from buffer.`)
}
+ static async #work_queue (task: Function): Promise<any> {
+ return new Promise((resolve, reject) => {
+ this.#queue.push({ task, resolve, reject })
+ this.#work_process()
+ })
+ }
+
+ static #work_process () {
+ const next = this.#queue.shift()
+ if (next && !this.#busy) {
+ const { task, resolve, reject } = next
+ this.#busy = true
+ task().then(resolve).catch(reject).finally(() => {
+ console.log('==== DONE ====')
+ this.#busy = false
+ this.#work_process()
+ })
+ }
+ }
+
/**
* Finds a nonce that satisfies the Nano proof-of-work requirements.
*
* @param {NanoPowOptions} options - Used to configure search execution
*/
static async work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse> {
+ return this.#work_queue(() => this.#work_generate(hash, options))
+ }
+ static async #work_generate (hash: string, options?: NanoPowOptions): Promise<WorkGenerateResponse> {
+ if (this.#debug) console.log('work_generate')
const { difficulty, effort } = await this.#work_init(null, hash, options)
let times = []
let start = performance.now()
+ let found = false
let nonce = 0n
let result = 0n
let random = BigInt(Math.floor(Math.random() * 0xffffffff))
seed = (seed & 0xffffffffn) << 32n | random
if (this.#debug) console.log('seed', seed.toString(16).padStart(16, '0'))
await this.#work_dispatch(this.#searchPipeline, seed, hash, difficulty, effort)
- const found = !!this.#resultView.getUint32(0)
+ found = !!this.#resultView.getUint32(0)
nonce = this.#resultView.getBigUint64(8, true)
result = this.#resultView.getBigUint64(16, true)
- this.#busy = !found
times.push(performance.now() - start)
- } while (this.#busy)
+ } while (!found)
if (this.#debug) {
console.log('nonce', nonce, nonce.toString(16).padStart(16, '0'))
* @param {NanoPowOptions} options - Options used to configure search execution
*/
static async work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse> {
+ return this.#work_queue(() => this.#work_validate(work, hash, options))
+ }
+ static async #work_validate (work: string, hash: string, options?: NanoPowOptions): Promise<WorkValidateResponse> {
+ if (this.#debug) console.log('work_validate')
const { difficulty } = await this.#work_init(work, hash, options)
let result = 0n
await this.#work_dispatch(this.#validatePipeline, seed, hash, difficulty, 1)
nonce = this.#resultView.getBigUint64(8, true)
result = this.#resultView.getBigUint64(16, true)
- this.#busy = false
if (seed !== nonce) throw new Error('Result does not match work')
if (this.#debug) {
console.log(`work_validate() output for good nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
expect.push(result)
- result = await NP.work_validate('326f310d629a8a98', '204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA', { difficulty: 0xffffffff00000000n, debug: isDebug })
- result = result.valid === '1' && result.valid_all === '1' && result.valid_receive === '1'
- console.log(`work_validate() output for good max difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
- expect.push(result)
-
result = await NP.work_validate('c5d5d6f7c5d6ccd1', '281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117', { debug: isDebug })
result = result.valid_all === '1'
console.log(`work_validate() output for colliding nonce is ${result === true ? 'correct' : 'incorrect'}`)
console.log(`work_validate() output for bad nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
expect.push(result)
- result = await NP.work_validate('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { difficulty: 0xffffffff00000000n, debug: isDebug })
- result = result.valid === '0' && result.valid_all === '0' && result.valid_receive === '1'
- console.log(`work_validate() output for bad max difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
+ try {
+ result = await NP.work_validate('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { difficulty: 0xfffffff800000001n, debug: isDebug })
+ console.log('boo')
+ } catch (err) {
+ result = null
+ }
+ result = result === null
+ console.log(`work_validate() output for bad difficulty beyond max is ${result === true ? 'correct' : 'incorrect'}`)
expect.push(result)
result = await NP.work_validate('29a9ae0236990e2e', '32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F', { debug: isDebug })
console.log(`work_validate() output for bad receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
expect.push(result)
- result = await NP.work_validate('e45835c3b291c3d1', '9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F', { difficulty: 0xffffffff00000000n, debug: isDebug })
- result = result.valid === '0' && result.valid_all === '1' && result.valid_receive === '1'
- console.log(`work_validate() output for send difficulty nonce that does not meet custom difficulty is ${result === true ? 'correct' : 'incorrect'}`)
- expect.push(result)
-
try {
if (!expect.every(result => result)) throw new Error(`Validation is not working`)
} catch (err) {
const check = await NP.work_validate(result.work, result.hash, { difficulty, debug: isDebug })
const isValid = (result.hash === hash && check.valid === '1') ? 'VALID' : 'INVALID'
times.push(end - start)
- const msg = `${isValid} [${result.work}] ${result.hash} (${end - start} ms)`
+ const msg = `${isValid} (${end - start} ms)\n${JSON.stringify(result, ' ', 2)}`
if (isOutputShown) document.getElementById('output').innerHTML += `${msg}<br/>`
}
document.getElementById('output').innerHTML += `<hr/>`