legalComments: 'inline',
outbase: 'src',
outdir: 'dist',
- dropLabels: process.env.NODE_ENV === 'development' ? [] : ['LOG'],
sourcemap: 'linked',
plugins: [
glsl({
CONFIG.EFFORT = +(process.env.NANO_POW_EFFORT ?? '') || CONFIG.EFFORT
CONFIG.PORT = process.send ? 0 : +(process.env.NANO_POW_PORT ?? '') || CONFIG.PORT
logger.isEnabled = CONFIG.DEBUG
- if (configFile.length === 0) LOG: logger.log('config file not found')
- LOG: logger.log(`config set: ${JSON.stringify(CONFIG)}`)
+ if (configFile.length === 0) logger.log('config file not found')
+ logger.log(`config set: ${JSON.stringify(CONFIG)}`)
}
await loadConfig()
// Initialize puppeteer
-LOG: logger.log('starting work server')
+logger.log('starting work server')
const NanoPow = await readFile(new URL('../index.js', import.meta.url), 'utf-8')
-LOG: logger.log('launching puppeteer browser')
+logger.log('launching puppeteer browser')
const browser = await launch({
handleSIGHUP: false,
handleSIGINT: false,
'--enable-unsafe-webgpu'
]
})
-LOG: logger.log('creating puppeteer page')
+logger.log('creating puppeteer page')
const page = await browser.newPage()
const src = `${NanoPow};window.NanoPow=NanoPow;`
<script type="module">${src}</script>
`
-LOG: logger.log('setting puppeteer request interception')
+logger.log('setting puppeteer request interception')
await page.setRequestInterception(true)
page.on('request', req => {
if (req.isInterceptResolutionHandled()) return
}
})
page.on('console', msg => logger.log(msg.text()))
-LOG: logger.log('navigating to https://nanopow.invalid/ to be intercepted')
+logger.log('navigating to https://nanopow.invalid/ to be intercepted')
await page.goto('https://nanopow.invalid/')
-LOG: logger.log('loading NanoPow handle')
+logger.log('loading NanoPow handle')
let NanoPowHandle = await page.waitForFunction(async () => {
await window.navigator.gpu.requestAdapter()
return window.NanoPow
})
-LOG: logger.log('puppeteer initialized')
+logger.log('puppeteer initialized')
// Track requests by IP address, and let them fall off over time
const requests = new Map<string, { time: number, tokens: number }>()
async function work (data: unknown): Promise<string> {
let resBody = 'request failed'
if (data == null || typeof data !== 'object') {
- LOG: logger.log('Failed to parse request')
+ logger.log('Failed to parse request')
throw new Error(resBody)
}
if (Object.getPrototypeOf(data) !== Object.prototype) {
- LOG: logger.log('Data corrupted.')
+ logger.log('Data corrupted.')
throw new Error(resBody)
}
const dataParsed = data as Record<string, unknown>
let { action, hash, work, difficulty } = dataParsed
if (action !== 'work_generate' && action !== 'work_validate') {
- LOG: logger.log('Action must be work_generate or work_validate.')
+ logger.log('Action must be work_generate or work_validate.')
throw new Error(resBody)
}
resBody = `${action} failed`
if (!isHex32(hash)) {
- LOG: logger.log('Hash must be hex char(64)')
+ logger.log('Hash must be hex char(64)')
throw new Error(resBody)
}
if (difficulty !== undefined && !isHex8(difficulty)) {
- LOG: logger.log('Difficulty must be hex char(16).')
+ logger.log('Difficulty must be hex char(16).')
throw new Error(resBody)
}
if (action === 'work_validate') {
if (!isHex8(work)) {
- LOG: logger.log('Work must be hex char(16).')
+ logger.log('Work must be hex char(16).')
throw new Error(resBody)
}
} else {
resBody = JSON.stringify(result)
return resBody
} catch (err) {
- LOG: logger.log(err)
+ logger.log(err)
throw new Error(resBody)
}
}
}
const client = requests.get(ip)
if (client && client.tokens-- <= 0) {
- LOG: logger.log(`==== Potential Abuse: ${ip} ====`)
+ logger.log(`==== Potential Abuse: ${ip} ====`)
return true
}
if (Date.now() - MAX_REQUEST_TIME > (client?.time ?? 0)) {
}
}
})
- LOG: logger.log(`server listening on ipc`)
+ logger.log(`server listening on ipc`)
process.send?.({ type: 'listening', data: 'ipc' })
} else {
server.listen(CONFIG.PORT, '127.0.0.1', () => {
const address = server.address()
if (address == null) {
- LOG: logger.log(`server closed`)
+ logger.log(`server closed`)
} else if (typeof address === 'string') {
- LOG: logger.log(`server listening on ${address}`)
+ logger.log(`server listening on ${address}`)
process.send?.({ type: 'listening', data: address })
} else {
CONFIG.PORT = address.port
- LOG: logger.log(`server listening on port ${address.port}`)
+ logger.log(`server listening on port ${address.port}`)
process.send?.({ type: 'listening', data: address.port })
}
})
})
server.on('error', serverErr => {
- LOG: logger.log('server error', serverErr)
+ logger.log('server error', serverErr)
try {
shutdown()
} catch (shutdownErr) {
- LOG: logger.log('server failed to shut down', shutdownErr)
+ logger.log('server failed to shut down', shutdownErr)
process.exit(1)
}
})
* does not respond within 10 seconds.
*/
function shutdown (): void {
- LOG: logger.log('shutdown signal received')
+ logger.log('shutdown signal received')
const kill = setTimeout((): never => {
- LOG: logger.log('server unresponsive, forcefully stopped')
+ logger.log('server unresponsive, forcefully stopped')
process.exit(1)
}, 10_000)
server.close(async (): Promise<never> => {
await browser.close()
clearTimeout(kill)
- LOG: logger.log('server stopped')
+ logger.log('server stopped')
process.exit(0)
})
}
process.on('SIGTERM', shutdown)
process.on('SIGHUP', async () => {
- LOG: logger.log('reloading configuration')
+ logger.log('reloading configuration')
server.close(async () => {
await loadConfig()
await page.reload()
export async function generate (hash: bigint, difficulty: bigint, debug: boolean): Promise<WorkGenerateResponse> {
logger.isEnabled = debug
- LOG: logger.groupStart('NanoPow CPU work_generate')
+ logger.groupStart('NanoPow CPU work_generate')
return new Promise((resolve, reject) => {
const check = () => {
try {
const seed = new BigUint64Array(1)
- LOG: logger.log('check 0x8000 nonces')
+ logger.log('check 0x8000 nonces')
let result = NanoPowValidate(crypto.getRandomValues(seed)[0], hash, difficulty, false)
for (let i = 0; i < 0x8000; i++) {
if (result.valid === '1') break
result = NanoPowValidate(crypto.getRandomValues(seed)[0], hash, difficulty, false)
}
if (result.valid === '1') {
- LOG: logger.groupEnd('NanoPow CPU work_generate')
+ logger.groupEnd('NanoPow CPU work_generate')
resolve({
hash: result.hash,
work: result.work,
try {
url = URL.createObjectURL(new Blob([NanoPowWasmWorker], { type: 'text/javascript' }))
workers = []
- LOG: logger.log(`NanoPow CPU initialized.`)
+ logger.log(`NanoPow CPU initialized.`)
isReady = true
} catch (err) {
isReady = false
}
try {
await workersStarted()
- LOG: logger.log('Workers ready')
+ logger.log('Workers ready')
} catch (err: any) {
throw new Error('Error loading workers')
}
w.onmessage = (msg) => {
const { data } = msg
if (data.url === url) {
- LOG: logger.log(`received result ${typeof data.result} "${data.result}" from worker ${i}`)
+ logger.log(`received result ${typeof data.result} "${data.result}" from worker ${i}`)
found(data.result)
}
}
- LOG: logger.log(`sending data to worker ${i}`, { ...data, url })
+ logger.log(`sending data to worker ${i}`, { ...data, url })
w.postMessage({ ...data, url })
}))
}
*/
export async function generate (hash: bigint, difficulty: bigint, effort: number, debug: boolean): Promise<WorkGenerateResponse> {
logger.isEnabled = debug
- LOG: logger.groupStart('NanoPow WASM work_generate')
- LOG: logger.log('generating')
+ logger.groupStart('NanoPow WASM work_generate')
+ logger.log('generating')
if (isReady === false) setup()
await init(hash, difficulty, effort)
work = await dispatch()
result = (await NanoPowValidate(work, hash, difficulty, debug)).difficulty
} catch (err) {
- LOG: logger.log(err)
+ logger.log(err)
} finally {
const isStopped = await workersStopped()
if (isStopped) {
- LOG: logger.log('workers stopped')
+ logger.log('workers stopped')
} else {
reset()
- LOG: logger.log('workers reset')
+ logger.log('workers reset')
}
- LOG: logger.groupEnd('NanoPow WASM work_generate')
+ logger.groupEnd('NanoPow WASM work_generate')
}
return {
//! SPDX-FileContributor: Ben Green <ben@latenightsketches.com>
//! SPDX-License-Identifier: GPL-3.0-or-later AND MIT
-import { bigHex, bigToU32, Logger } from '#utils'
+import { Logger, bigHex, bigToU32 } from '#utils'
import { WorkGenerateResponse } from 'nano-pow'
import { downsampleSource, drawSource, quadSource } from './shaders'
// Create persistent resizable canvas and get WebGL2 context
function createCanvas (size: number): void {
- LOG: logger.groupStart('NanoPow WebGL createCanvas')
+ logger.groupStart('NanoPow WebGL createCanvas')
if (canvas == null) {
canvas = new OffscreenCanvas(0, 0)
canvas.addEventListener('webglcontextlost', ev => {
throw new Error('NanoPow could not restore WebGL context.')
}, 10_000)
ev.preventDefault()
- LOG: logger.log('NanoPow WebGL createCanvas', 'WebGL context lost. Waiting for it to be restored...')
+ logger.log('NanoPow WebGL createCanvas', 'WebGL context lost. Waiting for it to be restored...')
})
canvas.addEventListener('webglcontextrestored', ev => {
window.clearTimeout(isContextLost)
isContextLost = 0
- LOG: logger.log('NanoPow WebGL createCanvas', 'WebGL context restored. Reinitializing...')
+ logger.log('NanoPow WebGL createCanvas', 'WebGL context restored. Reinitializing...')
})
}
gl = context
const MAX_VIEWPORT_DIMS = gl.getParameter(gl.MAX_VIEWPORT_DIMS) ?? [0x1000, 0x1000]
- LOG: logger.log('NanoPow WebGL createCanvas', 'requested size', size)
- LOG: logger.log('NanoPow WebGL createCanvas', 'MAX_VIEWPORT_DIMS', MAX_VIEWPORT_DIMS)
+ logger.log('NanoPow WebGL createCanvas', 'requested size', size)
+ logger.log('NanoPow WebGL createCanvas', 'MAX_VIEWPORT_DIMS', MAX_VIEWPORT_DIMS)
size = Math.min(size, ...MAX_VIEWPORT_DIMS)
size = (size >>> 8) << 8
canvas.height = canvas.width = size
- LOG: logger.log('NanoPow WebGL createCanvas', 'canvas size', canvas.height, canvas.width)
- LOG: logger.log('NanoPow WebGL createCanvas', 'drawingBuffer size', gl.drawingBufferHeight, gl.drawingBufferWidth)
+ logger.log('NanoPow WebGL createCanvas', 'canvas size', canvas.height, canvas.width)
+ logger.log('NanoPow WebGL createCanvas', 'drawingBuffer size', gl.drawingBufferHeight, gl.drawingBufferWidth)
if (canvas.height !== gl.drawingBufferHeight
|| canvas.width !== gl.drawingBufferWidth
) {
size = ((gl.drawingBufferHeight < gl.drawingBufferWidth ? gl.drawingBufferHeight : gl.drawingBufferWidth) >>> 8) << 8
canvas.height = canvas.width = size
}
- LOG: logger.log('NanoPow WebGL createCanvas', 'final size', size)
- LOG: logger.groupEnd('NanoPow WebGL createCanvas')
+ logger.log('NanoPow WebGL createCanvas', 'final size', size)
+ logger.groupEnd('NanoPow WebGL createCanvas')
}
function compile (): void {
// Finalize configuration
pixels = new Uint32Array(finalFbo.size.x * finalFbo.size.y * 4)
isReady = true
- LOG: logger.log(`NanoPow WebGL initialized at ${gl.drawingBufferWidth}x${gl.drawingBufferHeight}. Maximum nonces checked per frame: ${gl.drawingBufferWidth * gl.drawingBufferHeight}`)
+ logger.log(`NanoPow WebGL initialized at ${gl.drawingBufferWidth}x${gl.drawingBufferHeight}. Maximum nonces checked per frame: ${gl.drawingBufferWidth * gl.drawingBufferHeight}`)
} catch (err) {
throw new Error('WebGL compilation failed.', { cause: err })
}
vertexShader = null
gl?.deleteProgram(drawProgram)
drawProgram = null
- LOG: logger.log('reset done')
+ logger.log('reset done')
}
function init (hash: Uint32Array, difficulty: bigint): void {
inputHashView.setUint32(i * 16, hash[i])
}
inputDifficultyView.setBigUint64(0, difficulty, true)
- LOG: logger.log('INPUT', inputArray.buffer.slice(0))
+ logger.log('INPUT', inputArray.buffer.slice(0))
gl.bindBuffer(gl.UNIFORM_BUFFER, inputBuffer)
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, inputView)
gl.bindBuffer(gl.UNIFORM_BUFFER, null)
if (gl == null) throw new Error('WebGL 2 is required to draw')
if (drawFbo == null) throw new Error('FBO is required to draw')
if (query == null) throw new Error('Query is required to draw')
- LOG: logger.log(bigHex(seed, 16))
+ logger.log(bigHex(seed, 16))
// Upload work seed buffer
inputSeedView.setBigUint64(0, seed, true)
- LOG: logger.log('INPUT', inputView.buffer.slice(0))
+ logger.log('INPUT', inputView.buffer.slice(0))
gl.bindBuffer(gl.UNIFORM_BUFFER, inputBuffer)
gl.bufferSubData(gl.UNIFORM_BUFFER, 136, inputSeedView)
gl.bindBuffer(gl.UNIFORM_BUFFER, null)
// Loop pixels to find one with values
for (let i = 0; i < pixels.length; i += 4) {
if (pixels[i] || pixels[i + 1] || pixels[i + 2] || pixels[i + 3]) {
- LOG: logger.log(`rgba(${pixels[i]}, ${pixels[i + 1]}, ${pixels[i + 2]}, ${pixels[i + 3]})`)
+ logger.log(`rgba(${pixels[i]}, ${pixels[i + 1]}, ${pixels[i + 2]}, ${pixels[i + 3]})`)
resultView.setUint32(0, pixels[i], true)
resultView.setUint32(4, pixels[i + 1], true)
resultView.setUint32(8, pixels[i + 2], true)
}
}
}
- LOG: logger.log(`pixels[${pixels.length}]`, pixels)
+ logger.log(`pixels[${pixels.length}]`, pixels)
throw new Error('Query reported result but nonce value not found')
}
let timeout = false
const kill = setTimeout(() => {
timeout = true
- LOG: logger.log('timed out')
+ logger.log('timed out')
}, 60_000)
- LOG: logger.groupStart('NanoPow WebGL work_generate')
- LOG: logger.log(`generate('${bigHex(hash, 16)}', '${bigHex(difficulty, 16)}', ${effort}, ${debug})`)
+ logger.groupStart('NanoPow WebGL work_generate')
+ logger.log(`generate('${bigHex(hash, 16)}', '${bigHex(difficulty, 16)}', ${effort}, ${debug})`)
// Start drawing to calculate one nonce per pixel
const seed = new BigUint64Array(4)
}
init(bigToU32(hash, 8), difficulty)
crypto.getRandomValues(seed)
- LOG: logger.log('drawing frame 0')
+ logger.log('drawing frame 0')
draw(seed[0], drawFbos[0], queries[0])
draw(seed[1], drawFbos[1], queries[1])
draw(seed[2], drawFbos[2], queries[2])
isFirstRetry = false
} catch (err) {
isFirstRetry = !isFirstRetry
- LOG: logger.log(err)
+ logger.log(err)
if (!isFirstRetry) {
throw new Error('failed to restore context', { cause: err })
}
clearTimeout(kill)
cancelAnimationFrame(raf)
raf = 0
- LOG: logger.groupEnd('NanoPow WebGL work_generate')
+ logger.groupEnd('NanoPow WebGL work_generate')
if (!found) throw new Error(timeout ? 'timed out' : 'work not found for unknown reason')
}
// Initialize WebGPU
async function start (): Promise<void> {
if (status === 'Idle') {
- LOG: logger.log('starting')
+ logger.log('starting')
status = 'Starting'
await getDevice()
try {
await compile()
- LOG: logger.log('ready')
+ logger.log('ready')
status = 'Ready'
} catch (err) {
status = 'Crashed'
- LOG: logger.log(err)
+ logger.log(err)
throw new Error('failed to compile', { cause: err })
}
}
throw new Error('failed to get device from gpu adapter')
}
device.lost?.then(async (deviceLostInfo) => {
- LOG: logger.log('device lost', deviceLostInfo)
+ logger.log('device lost', deviceLostInfo)
// Set up 30s timeout to prevent long-running calls
isContextLost = window.setTimeout(() => {
throw new Error('failed to restore device', { cause: deviceLostInfo })
}
async function restore (): Promise<void> {
- LOG: logger.log('restoring')
+ logger.log('restoring')
try {
status = 'Restoring'
for (let i = 0; i < 2; i++) {
await compile()
window.clearTimeout(isContextLost)
isContextLost = 0
- LOG: logger.log('ready')
+ logger.log('ready')
status = 'Ready'
} catch (err) {
status = 'Crashed'
}
async function init (hash: bigint, difficulty: bigint): Promise<void> {
- LOG: logger.log('variables initializing')
+ logger.log('variables initializing')
try {
// Save hash data for normal usage and potential recovery efforts
hashData.set(bigToU64(hash, 4))
device.queue.writeBuffer(outputBuffers[0], 0, bufferReset)
device.queue.writeBuffer(outputBuffers[1], 0, bufferReset)
} catch (err) {
- LOG: logger.log(err)
+ logger.log(err)
throw new Error('failed to initialize', { cause: err })
}
}
async function dispatch (dispatchIndex: number, seed: bigint, effort: number): Promise<void> {
- LOG: logger.log('dispatching compute pass')
+ logger.log('dispatching compute pass')
try {
- LOG: logger.log('seed', bigHex(seed, 16))
+ logger.log('seed', bigHex(seed, 16))
// Copy seed into INPUT buffer
inputDataView.setBigUint64(40, seed, true)
- LOG: logger.log('INPUT', inputDataView)
+ logger.log('INPUT', inputDataView)
device.queue.writeBuffer(inputBuffers[dispatchIndex], 40, inputDataView, 40)
// Create command encoder to issue commands to GPU and initiate computation
// End computation by passing array of command buffers to command queue for execution
device.queue.submit([commandEncoder.finish()])
} catch (err) {
- LOG: logger.log(err)
+ logger.log(err)
throw new Error('failed to dispatch compute pass', { cause: err })
}
}
async function check (dispatchIndex: number): Promise<boolean> {
- LOG: logger.log('checking results from compute pass')
+ logger.log('checking results from compute pass')
try {
await resultBuffers[dispatchIndex].mapAsync(GPUMapMode.READ)
resultViews[dispatchIndex] = new DataView(resultBuffers[dispatchIndex].getMappedRange().slice(0))
resultBuffers[dispatchIndex].unmap()
- LOG: logger.log('OUTPUT', resultViews[dispatchIndex])
+ logger.log('OUTPUT', resultViews[dispatchIndex])
if (resultViews[dispatchIndex] == null) throw new Error('failed to get data from resultBuffer.')
return !!resultViews[dispatchIndex].getUint32(0, true)
} catch (err) {
- LOG: logger.log(err)
+ logger.log(err)
throw new Error('failed to read results from compute pass', { cause: err })
}
}
* Map CPU buffer to GPU, read results to static result object, and unmap.
*/
function read (dispatchIndex: number): { work: bigint, difficulty: bigint } {
- LOG: logger.log('reading results from compute pass')
+ logger.log('reading results from compute pass')
try {
if (resultViews[dispatchIndex] == null) throw new Error('failed to get data from result view')
return {
difficulty: resultViews[dispatchIndex].getBigUint64(16, true)
}
} catch (err) {
- LOG: logger.log(err)
+ logger.log(err)
throw new Error('failed to read results from compute pass', { cause: err })
}
}
timeout = true
throw new Error('timed out')
}, 60_000)
- LOG: logger.groupStart('NanoPow WebGPU work_generate')
- LOG: logger.log('generating')
+ logger.groupStart('NanoPow WebGPU work_generate')
+ logger.log('generating')
const seed = new BigUint64Array(2)
let found = false
let result: Record<string, bigint> = {}
}
// Only retry once here, allow errors to propagate out to the consumer
isFirstRetry = !isFirstRetry
- LOG: logger.log(err)
+ logger.log(err)
if (!isFirstRetry) {
throw new Error('failed to restore device', { cause: err })
}
} while (isFirstRetry)
} finally {
clearTimeout(kill)
- LOG: logger.groupEnd('NanoPow WebGPU work_generate')
+ logger.groupEnd('NanoPow WebGPU work_generate')
if (!found && timeout) throw new Error('timed out')
}
import { NanoPowConfig } from '#lib/config'
import { NanoPowCpu, NanoPowWasm, NanoPowWebgl, NanoPowWebgpu } from '#lib/generate'
import { NanoPowValidate } from '#lib/validate'
-import { big, Cache, Logger, MAX_HASH, Queue } from '#utils'
+import { Cache, Logger, MAX_HASH, Queue, big } from '#utils'
import { WorkErrorResponse, WorkGenerateResponse, WorkValidateResponse } from 'nano-pow'
const logger = new Logger()
const { api, debug, difficulty, effort } = await NanoPowConfig(options)
const hash = big(blockhash)
if (hash < 0 || hash > MAX_HASH) throw RangeError('invalid block hash', { cause: blockhash })
- LOG: logger.isEnabled = debug
+ logger.isEnabled = debug
const cached = Cache.search(hash, difficulty)
if (cached) {
- LOG: logger.log('found work in cache')
+ logger.log('found work in cache')
const { valid } = NanoPowValidate(big(cached.work), big(cached.hash), difficulty, debug)
if (valid === '1') {
return cached
try {
Cache.delete(hash)
} catch (err) {
- LOG: logger.log('error deleting invalid work from cache, continuing with generate', err)
+ logger.log('error deleting invalid work from cache, continuing with generate', err)
}
}
}
}
function log (work: bigint, hash: bigint, difficulty: bigint): void {
- LOG: logger.groupStart('NanoPow CPU work_validate')
- LOG: logger.log('NanoPow CPU work_validate', 'work', bigHex(work, 16))
- LOG: logger.log('NanoPow CPU work_validate', 'hash', bigHex(hash, 64))
- LOG: logger.log('NanoPow CPU work_validate', 'difficulty', bigHex(difficulty, 16))
- LOG: logger.log('NanoPow CPU work_validate', 'result', bigHex(result, 16))
- LOG: logger.groupEnd('NanoPow CPU work_validate')
+ logger.groupStart('NanoPow CPU work_validate')
+ logger.log('NanoPow CPU work_validate', 'work', bigHex(work, 16))
+ logger.log('NanoPow CPU work_validate', 'hash', bigHex(hash, 64))
+ logger.log('NanoPow CPU work_validate', 'difficulty', bigHex(difficulty, 16))
+ logger.log('NanoPow CPU work_validate', 'result', bigHex(result, 16))
+ logger.groupEnd('NanoPow CPU work_validate')
}
function validate (work: bigint, hash: bigint, difficulty: bigint, debug: boolean): WorkValidateResponse {