\fB--server\fR
Start work server (see SERVER below). Must be the first argument in order to be recognized.
.TP
-\fB\-h\fR, \fB\-\-help\fR
-Show this help dialog and exit.
-.TP
-\fB\-\-debug\fR
-Enable additional logging output.
-.TP
-\fB\-\-benchmark\fR \fICOUNT\fR
-Generate work for the specified number of random hashes.
-.TP
\fB\-b\fR, \fB\-\-batch\fR
Format final output of all hashes as a JSON array instead of incrementally returning an object for each result as soon as it is calculated.
.TP
.TP
\fB\-v\fR, \fB\-\-validate\fR \fIWORK\fR
Check an existing work value instead of searching for one. If you pass multiple blockhashes, the work value will be validated against each one.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Show this help dialog and exit.
+.TP
+\fB\-\-debug\fR
+Enable additional logging output.
+.TP
+\fB\-\-benchmark\fR \fICOUNT\fR
+Generate work for the specified number of random hashes.
.SH SERVER
Calling \fBnano-pow\fR with the \fI--server\fR option will start the NanoPow work server in a detached process.
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
//! SPDX-License-Identifier: GPL-3.0-or-later
+import { spawn } from 'node:child_process'
import { getRandomValues } from 'node:crypto'
import { createInterface } from 'node:readline/promises'
import type { WorkGenerateResponse, WorkValidateResponse } from '#types'
process.title = 'NanoPow CLI'
delete process.env.NANO_POW_DEBUG
-process.env.NANO_POW_EFFORT = ''
-process.env.NANO_POW_PORT = '5041'
+delete process.env.NANO_POW_EFFORT
+delete process.env.NANO_POW_PORT
function log (...args: any[]): void {
if (process.env.NANO_POW_DEBUG) console.log(new Date(Date.now()).toLocaleString(), 'NanoPow', args)
Prints the result as a Javascript object to standard output as soon as it is calculated.
If using --batch, results are printed only after all BLOCKHASH(es) have be processed.
If using --validate, results will also include validity properties.
-
- -h, --help show this dialog
- --debug enable additional logging output
- --benchmark <value> generate work for specified number of random hashes
-
-b, --batch process all data before returning final results as array
-d, --difficulty <value> override the minimum difficulty value
-e, --effort <value> increase demand on GPU processing
-v, --validate <value> check an existing work value instead of searching for one
+ -h, --help show this dialog
+ --debug enable additional logging output
+ --benchmark <value> generate work for specified number of random hashes
+
If validating a nonce, it must be a 16-character hexadecimal value.
Effort must be a decimal number between 1-32.
Difficulty must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.
// Initialize server
log('Starting NanoPow CLI')
-await import('./server.js')
+const server = spawn(process.execPath, [new URL(import.meta.resolve('./server.js')).pathname], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] })
+const port = await new Promise((resolve, reject): void => {
+ server.on('message', (msg: { type: string, port: number }): void => {
+ if (msg.type === 'listening') {
+ if (msg.port != null) {
+ log(`Server listening on port ${msg.port}`)
+ resolve(msg.port)
+ } else {
+ reject('Server failed to provide port')
+ }
+ }
+ })
+})
// Execution must be sequential else GPU cannot map to CPU and will throw
const results: (WorkGenerateResponse | WorkValidateResponse)[] = []
try {
body.hash = hash
const kill = setTimeout(() => aborter.abort(), 60000)
- const response = await fetch(`http://localhost:${process.env.NANO_POW_PORT}`, {
+ const response = await fetch(`http://localhost:${port}`, {
method: 'POST',
body: JSON.stringify(body),
signal: aborter.signal
if (process.env.NANO_POW_DEBUG || isBenchmark) {
console.log(end - start, 'ms total |', (end - start) / hashes.length, 'ms avg')
}
-process.exit(0)
+server.on('close', code => {
+ log(`Server closed with exit code ${code}`)
+ process.exit(code)
+})
+server.kill()
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
//! SPDX-License-Identifier: GPL-3.0-or-later
-import { launch, Browser, Page } from 'puppeteer'
+import { launch } from 'puppeteer'
import { subtle } from 'node:crypto'
-import { lookup } from 'node:dns/promises'
import { readFile, unlink, writeFile } from 'node:fs/promises'
import * as http from 'node:http'
-import { homedir, hostname } from 'node:os'
+import { AddressInfo } from 'node:net'
+import { homedir } from 'node:os'
import { join } from 'node:path'
import type { NanoPowOptions, WorkGenerateRequest, WorkGenerateResponse, WorkValidateRequest, WorkValidateResponse } from '#types'
+/**
+* Override console logging to provide an informative prefix for each entry and
+* to only output when debug mode is enabled.
+*/
+function log (...args: any[]): void {
+ if (CONFIG.DEBUG) console.log(new Date(Date.now()).toLocaleString(), 'NanoPow', args)
+}
+
process.title = 'NanoPow Server'
const MAX_REQUEST_SIZE = 1024
const MAX_BODY_SIZE = 158
-let DEBUG: boolean = !!(process.env.NANO_POW_DEBUG || false)
-let EFFORT: number = +(process.env.NANO_POW_EFFORT || 8)
-let PORT: number = +(process.env.NANO_POW_PORT || 5040)
-
-let browser: Browser
-let page: Page
-
-function log (...args: any[]): void {
- if (DEBUG) console.log(new Date(Date.now()).toLocaleString(), 'NanoPow', args)
+const CONFIG = {
+ DEBUG: false,
+ EFFORT: 8,
+ PORT: 5040
}
-async function loadConfig () {
- const contents = await readFile(join(homedir(), '.nano-pow', 'config'), 'utf-8')
+/**
+* Loads the server configuration, preferring environment variables over the
+* config file in the `.nano-pow` directory, and falling back to default values
+* if no values are provided. Port will load initially, but changing it while the
+* server is running will require a server restart to take effect.
+*/
+async function loadConfig (): Promise<void> {
+ let contents = null
+ try {
+ contents = await readFile(join(homedir(), '.nano-pow', 'config'), 'utf-8')
+ } catch (err) {
+ log('Config file not found')
+ }
if (typeof contents === 'string') {
- const config = contents.split('\n')
- for (const line of config) {
+ for (const line of contents.split('\n')) {
const debugMatch = line.match(/^[ \t]*debug[ \t]*(true|false)[ \t]*(#.*)?$/i)
if (Array.isArray(debugMatch)) {
- DEBUG = !!(process.env.NANO_POW_DEBUG) || debugMatch?.[1] === 'true' || false
+ CONFIG.DEBUG = debugMatch[1] === 'true'
}
-
const effortMatch = line.match(/^[ \t]*effort[ \t]*(\d{1,2})[ \t]*(#.*)?$/i)
if (Array.isArray(effortMatch)) {
- EFFORT = +(process.env.NANO_POW_EFFORT || effortMatch?.[1] || 8)
+ CONFIG.EFFORT = +effortMatch[1]
}
const portMatch = line.match(/^[ \t]*port[ \t]*(\d{1,5})[ \t]*(#.*)?$/i)
if (Array.isArray(portMatch)) {
- PORT = +(process.env.NANO_POW_PORT || portMatch?.[1] || 5040)
+ CONFIG.PORT = +portMatch[1]
}
}
}
+ CONFIG.DEBUG = !!(process.env.NANO_POW_DEBUG) || CONFIG.DEBUG
+ CONFIG.EFFORT = +(process.env.NANO_POW_EFFORT ?? '') || CONFIG.EFFORT
+ CONFIG.PORT = process.send ? 0 : +(process.env.NANO_POW_PORT ?? '') || CONFIG.PORT
}
await loadConfig()
+process.on('SIGHUP', async (): Promise<void> => {
+ log('Reloading configuration')
+ await loadConfig()
+})
async function respond (res: http.ServerResponse, data: Buffer[]): Promise<void> {
let statusCode: number = 500
}
response = `${action} failed`
const options: NanoPowOptions = {
- debug: DEBUG,
- effort: EFFORT,
+ debug: CONFIG.DEBUG,
+ effort: CONFIG.EFFORT,
difficulty
}
const args = []
log('Server unresponsive, forcefully stopped')
process.exit(1)
}, 10000)
- server.close((): Promise<never> => {
+ server.close(async (): Promise<never> => {
+ await browser.close()
clearTimeout(kill)
log('Server stopped')
process.exit(0)
// Initialize puppeteer
log('Starting NanoPow work server')
const NanoPow = await readFile(new URL('../main.min.js', import.meta.url), 'utf-8')
-browser = await launch({
+const browser = await launch({
+ handleSIGHUP: false,
+ handleSIGINT: false,
+ handleSIGTERM: false,
headless: true,
args: [
'--headless=new',
'--enable-unsafe-webgpu'
]
})
-page = await browser.newPage()
+const page = await browser.newPage()
page.on('console', msg => log(msg.text()))
const path: string = new URL(import.meta.url).pathname
log('Puppeteer initialized')
// Listen on configured port
-server.listen(PORT, async (): Promise<void> => {
- const ip = await lookup(hostname(), { family: 4 })
- log(`Server process ${process.pid} running at ${ip.address}:${PORT}/`)
+server.listen(CONFIG.PORT, async (): Promise<void> => {
+ const { port } = server.address() as AddressInfo
+ log(`Server process ${process.pid} listening on port ${port}`)
+ process.send?.({ type: 'listening', port })
})