From 0342cd98c6a738f2406834758f392016a4c52d90 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 26 Mar 2025 08:04:11 -0700 Subject: [PATCH] Ignore all arguments when starting server and use envvars instead. Read NANO_POW_EFFORT from environment when starting server. Update test script to use port 3001 to test custom value. Pass NanoPowOptions object into puppeteer evaluate calls. Update README to use destructuring assignment for output clarity and to use new method names. Add more command line guidance to README. Fix default effort used by server to match the actual implementation and fix README accordingly. Add --server documentation to manual. Fix threshold/difficulty ranges throughout documentation and remove mention of u32 threshold workaround from README. Whitespace. --- README.md | 131 +++++++++++++++++++++++++++++++++++--------- docs/index.js | 4 +- docs/nano-pow.1 | 85 +++++++++++++++++++++++++--- src/bin/cli.ts | 1 + src/bin/nano-pow.sh | 2 +- src/bin/server.ts | 24 ++++---- test/script.sh | 35 ++++++------ 7 files changed, 215 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 5a8664c..ba02a2a 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,10 @@ https://docs.nano.org/integration-guides/work-generation/#work-calculation-detai ## Installation ```console -npm i nano-pow +$ npm i nano-pow ``` +NanoPow can also be installed globally to add the `nano-pow` command to your +environment. To learn more, see [#Executables](#executables). ## Usage ### Import @@ -46,17 +48,17 @@ Use it directly on a webpage with a script module: ``` -### Search +### Generate ```javascript // `hash` is a 64-char hex string const hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' -const work = await NanoPow.search(hash) +const { work } = await NanoPow.work_generate(hash) // Result is a 16-char hex string ``` @@ -66,29 +68,112 @@ const work = await NanoPow.search(hash) const work = 'fedcba0987654321' // `hash` is a 64-char hex string const hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' -const isValid = await NanoPow.validate(work, hash) +const { valid } = await NanoPow.work_validate(work, hash) // Result is a boolean ``` ### Options ```javascript const options = { - // default 0xFFFFFFF8 for send/change blocks + // default 0xFFFFFFF800000000 for send/change blocks threshold: number, // default 8, valid range 1-32 effort: number, // default false debug: true } -const work = await NanoPow.search(hash, options) +const { work } = await NanoPow.work_generate(hash, options) ``` -### Command Line +## Executables NanoPow can be installed globally and executed from the command line. This is useful for systems without a graphical interface. ```console $ npm -g i nano-pow -$ nano-pow --help # view command documentation +$ nano-pow --help # view abbreviated CLI help +$ man nano-pow # view full manual +``` +Ensure you have proper permissions on your +[npm `prefix`](https://docs.npmjs.com/cli/v11/commands/npm-prefix) directory and +have configured your `PATH` accordingly. + +For example, this adds a user-specific directory for local binaries to `PATH` +upon login and configures `npm prefix` to use it for global installations: +```console +$ echo 'PATH="$HOME/.local/bin:$PATH"' >> .bashrc +$ npm config set prefix="$HOME/.local" +``` +### Command Line +NanoPow provides a shell command—`nano-pow`—to accomodate systems +without a graphical user interface. It launches a headless Chrome browser using +`puppeteer` to access the required WebGPU or WebGL APIs. Use the `--global` flag +when installing to add the executable script to your system. +```console +$ npm i -g nano-pow +``` +Some examples are provided below, and for full documentation, read the manual +with `man nano-pow`. + +```console +$ # Generate a work value using default settings and debugging output enabled. +$ nano-pow --debug 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +``` +```console +$ # Generate work using customized behavior with options. +$ nano-pow --effort 32 --threshold FFFFFFC000000000 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +``` +```console +$ # Validate an existing work nonce against a blockhash. +nano-pow --validate fedcba9876543210 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +``` +```console +$ # Process blockhashes in batches to reduce the initial startup overhead. +$ nano-pow 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef [...] +$ # OR +nano-pow $(cat /path/to/hashes/file) +$ # OR +$ cat /path/to/hashes/file | nano-pow +``` + +### Server +NanoPow also provides a basic work server similar to the one included in the +official Nano node software. The installed command will launch the server in a +detached process, and you can also start it yourself to customize behavior by +executing the server script directly. + +`PORT` can be passed as an environment variable and defaults to 3000 if not +specified. + +`NANO_POW_EFFORT` can also be passed as an environment variable to increase +or decrease the demand on the GPU and defaults to 8 if not specified. + +```console +$ # Launch the server and detach from the current session +$ PORT=8080 nano-pow --server +$ # View process ID for "NanoPow Server" +$ cat ~/.nano-pow/server.pid +$ # Display list of server logs +$ ls ~/.nano-pow/logs/ +$ # Find process ID manually +$ ps aux | grep NanoPow +``` +Work is generated or validated by sending an HTTP `POST` request to the +configured hostname or IP address of the machine. Some basic help is available +via `GET` request. +```console +$ # Generate a work value +$ curl -d '{ + "action": "work_generate", + "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +}' localhost:3000 +``` +```console +$ # Validate a work value +$ curl -d '{ + "action": "work_validate", + "work": "e45835c3b291c3d1", + "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +}' localhost:3000 ``` ## Notes @@ -106,21 +191,16 @@ for all other accounts. * 𝘵𝘩𝘳𝘦𝘴𝘩𝘰𝘭𝘥 is 0xFFFFFFF800000000 for send/change blocks and 0xFFFFFE0000000000 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. - 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 -consequently immense, but the goal is to squeeze every last bit of speed and -performance out of it. +due to the very narrow use case to which it is applied. The compute shader used +by the WebGPU implementation is consequently immense, but the goal is to squeeze +every last bit of speed and performance out of it. ## Tests -`test.html` in the source repository contains some basic tests to compare the -speed of this tool. Feel free to check out how your system fares. +A few basic tests are availabe in the source repository. +* `test/index.html` in the source repository contains a web interface to change +execution options and compare results. +* `test/script.sh` starts the `nano-pow` server and sends some basic requests. ## Building 1. Clone source @@ -128,11 +208,10 @@ speed of this tool. Feel free to check out how your system fares. 1. Install dev dependencies 1. Compile, minify, and bundle -```bash -git clone https://zoso.dev/nano-pow.git -cd nano-pow -npm i -npm run build +```console +$ git clone https://zoso.dev/nano-pow.git +$ cd nano-pow +$ npm i ``` ## Reporting Bugs diff --git a/docs/index.js b/docs/index.js index da2b3f7..5e10410 100644 --- a/docs/index.js +++ b/docs/index.js @@ -8,14 +8,14 @@ Prints a 16-character hexadecimal work value to standard output. If using --vali -h, --help show this dialog -d, --debug enable additional logging output - -j, --json format output as JSON + -j, --json gather all results and output them at once as JSON -e, --effort= increase demand on GPU processing -t, --threshold= override the minimum threshold value -v, --validate= check an existing work value instead of searching for one If validating a nonce, it must be a 16-character hexadecimal value. Effort must be a decimal number between 1-32. -Threshold must be a hexadecimal string between 0-FFFFFFFF. +Threshold must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF. Report bugs: Full documentation: diff --git a/docs/nano-pow.1 b/docs/nano-pow.1 index aa67ac2..2c0ff2b 100644 --- a/docs/nano-pow.1 +++ b/docs/nano-pow.1 @@ -12,12 +12,19 @@ nano-pow \- proof-of-work generation and validation for Nano cryptocurrency .SH DESCRIPTION Generate work for \fIBLOCKHASH\fR, or multiple work values for \fIBLOCKHASH\fR(es). .PP +If the \fI--server\fR option is provided as the first argument, all other options are ignored and the NanoPow server is started. +.PP \fIBLOCKHASH\fR is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks. .PP -Prints a 16-character hexadecimal work value to standard output. If \fB--validate\fR is used, prints 'true' or 'false' to standard output. +Prints a 16-character hexadecimal work value, the BLAKE2b result, and the originating hash to standard output as a Javascript object. +.PP +If \fB--validate\fR is used, the original work value is returned instead along with validation flags. .SH OPTIONS .TP +\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 @@ -25,20 +32,64 @@ Show this help dialog and exit. Enable additional logging output. .TP \fB\-j\fR, \fB\-\-json\fR -Format output as JSON. +Format final output of all hashes as a JSON array of stringified values instead of incrementally returning Javascript objects for each result. .TP \fB\-e\fR, \fB\-\-effort\fR=\fIEFFORT\fR Increase demand on GPU processing. Must be between 1 and 32 inclusive. .TP \fB\-t\fR, \fB\-\-threshold\fR=\fITHRESHOLD\fR -Override the minimum threshold value. Higher values increase difficulty. Must be a hexadecimal string between 0 and FFFFFFFF inclusive. +Override the minimum threshold value. Higher values increase difficulty. Must be a hexadecimal string between 1 and FFFFFFFFFFFFFFFF inclusive. .TP \fB\-v\fR, \fB\-\-validate\fR=\fIWORK\fR -Check an existing work value instead of searching for one. +Check an existing work value instead of searching for one. If you pass multiple blockhashes, the work value will be validated against each one. + +.SH SERVER +Calling \fBnano-pow\fR with the \fI--server\fR option will start the NanoPow work server in a detached process. +.PP +It provides work generation and validation via HTTP requests in a similar, but not identical, fashion as the official Nano node. +.PP +More specifically, it does not support the \fIuse_peers\fR, \fImultiplier\fR, \fIaccount\fR, \fIversion\fR, \fIblock\fR, and \fIjson_block\fR options. +.PP +By default, the server listens on port 3000. To use a different port, set the \fBPORT\fR environment variable before starting the server. + +.PP +.EX +$ PORT=8080 nano-pow --server +.EE + +.PP +The server process ID (PID) is saved to \fB~/.nano-pow/server.pid\fR. Log files are stored in \fB~/.nano-pow/logs/\fR. +.TP +To stop the server, terminate it using its PID. +.EX +$ cat ~/.nano-pow/server.pid +12345 +$ kill 12345 +.EE +.TP +Alternatively, use \fBpgrep\fR to find the PID by process name: +.EX +$ pgrep "NanoPow Server" +12345 +$ kill $(pgrep "NanoPow Server") +.EE + +.PP +The server accepts HTTP \fBPOST\fR requests to the server's root path (\fB/\fR). Requests should be in JSON format and include an \fBaction\fR field to specify the desired operation. +.PP +The following actions are supported: +.TP +\fBwork_generate\fR +Generate a work value for a given blockhash. Requires a \fBhash\fR field containing the 64-character hexadecimal blockhash. +.TP +\fBwork_validate\fR +Validate an existing work value against a blockhash. Requires \fBwork\fR field containing the 16-character hexadecimal work value and a \fBhash\fR field containing the 64-character hexadecimal blockhash. +.PP + -.SH EXAMPLES +.SH EXAMPLES - CLI .PP -Search for a work nonce for a blockhash using the default threshold 0xFFFFFFF8: +Search for a work nonce for a blockhash using the default threshold 0xFFFFFFF800000000: .EX $ nano-pow \fB0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR .EE @@ -46,7 +97,7 @@ $ nano-pow \fB0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\f .PP Search for a work nonce using a custom threshold and increased effort: .EX -$ nano-pow \fB\-t fffffe00 \-e 32 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR +$ nano-pow \fB\-t fffffe0000000000 \-e 32 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR .EE .PP @@ -61,6 +112,26 @@ Validate an existing work nonce against a blockhash and show debugging output: $ nano-pow \fB\-d \-v fedcba9876543210 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR .EE +.SH EXAMPLES - SERVER +.PP +Generate a work value: +.EX +$ curl -d '{ + "action": "work_generate", + "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +}' localhost:3000 +.EE + +.PP +Validate a work value: +.EX +$ curl -d '{ + "action": "work_validate", + "work": "e45835c3b291c3d1", + "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +}' localhost:3000 +.EE + .SH AUTHOR Written by Chris Duncan. diff --git a/src/bin/cli.ts b/src/bin/cli.ts index 9d0f518..5181b65 100755 --- a/src/bin/cli.ts +++ b/src/bin/cli.ts @@ -2,6 +2,7 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later /// + import * as crypto from 'node:crypto' import * as fs from 'node:fs/promises' import * as readline from 'node:readline/promises' diff --git a/src/bin/nano-pow.sh b/src/bin/nano-pow.sh index c0a4e4a..4337ead 100755 --- a/src/bin/nano-pow.sh +++ b/src/bin/nano-pow.sh @@ -10,7 +10,7 @@ NANO_POW_LOGS="$NANO_POW_HOME"/logs; mkdir -p "$NANO_POW_LOGS"; if [ "$1" = '--server' ]; then shift; - node "$SCRIPT_DIR"/server.js "$@" > "$NANO_POW_LOGS"/nano-pow-server-$(date +%s).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid; + node "$SCRIPT_DIR"/server.js > "$NANO_POW_LOGS"/nano-pow-server-$(date +%s).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid; else node "$SCRIPT_DIR"/cli.js "$@"; fi; diff --git a/src/bin/server.ts b/src/bin/server.ts index 834617f..9f4078f 100755 --- a/src/bin/server.ts +++ b/src/bin/server.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later + import * as crypto from 'node:crypto' import * as dns from 'node:dns/promises' import * as fs from 'node:fs/promises' @@ -11,6 +12,7 @@ import { serverHelp } from '../../docs/index.js' import { NanoPowOptions, WorkGenerateRequest, WorkGenerateResponse, WorkValidateRequest, WorkValidateResponse } from '../types.js' const PORT = process.env.PORT || 3000 +const EFFORT = +(process.env.NANO_POW_EFFORT || 8) function log (...args) { console.log(new Date(Date.now()).toLocaleString(), 'NanoPow', args) @@ -37,14 +39,11 @@ async function work_generate (res: http.ServerResponse, json: WorkGenerateReques } try { - const result = await page.evaluate(async (args: WorkGenerateRequest): Promise => { - const options: NanoPowOptions = { - debug: true - } - if (args.difficulty) options.threshold = BigInt(`0x${args.difficulty}`) + const result = await page.evaluate(async (json: WorkGenerateRequest, options: NanoPowOptions): Promise => { + if (json.difficulty) options.threshold = BigInt(`0x${json.difficulty}`) // @ts-expect-error - return await window.NanoPow.work_generate(args.hash, options) - }, json) + return await window.NanoPow.work_generate(json.hash, options) + }, json, { debug: true, effort: EFFORT }) res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(result)) } catch (err) { @@ -72,14 +71,11 @@ async function work_validate (res: http.ServerResponse, json: WorkValidateReques } try { - const result: WorkValidateResponse = await page.evaluate(async (args: WorkValidateRequest): Promise => { - const options: NanoPowOptions = { - debug: true - } - if (args.difficulty) options.threshold = BigInt(`0x${args.difficulty}`) + const result: WorkValidateResponse = await page.evaluate(async (json: WorkValidateRequest, options: NanoPowOptions): Promise => { + if (json.difficulty) options.threshold = BigInt(`0x${json.difficulty}`) //@ts-expect-error - return await window.NanoPow.work_validate(args.work, args.hash, options) - }, json) + return await window.NanoPow.work_validate(json.work, json.hash, options) + }, json, { debug: true, effort: EFFORT }) res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(result)) } catch (err) { diff --git a/test/script.sh b/test/script.sh index 4aea454..6376fe9 100755 --- a/test/script.sh +++ b/test/script.sh @@ -1,38 +1,39 @@ # SPDX-FileCopyrightText: 2025 Chris Duncan # SPDX-License-Identifier: GPL-3.0-or-later +export NANO_POW_EFFORT=24 SCRIPT_LINK=$(readlink -f "$0"); SCRIPT_DIR=$(dirname "$SCRIPT_LINK"); NANO_POW_HOME="$HOME"/.nano-pow; NANO_POW_LOGS="$NANO_POW_HOME"/logs; -"$SCRIPT_DIR"/../dist/bin/nano-pow.sh --server +PORT=3001 "$SCRIPT_DIR"/../dist/bin/nano-pow.sh --server sleep 2s printf '\nGet documentation\n' -curl localhost:3000 +curl localhost:3001 printf '\nExpect error. Server should not crash when bad data is received like missing end quote\n' -curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash: "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D }' localhost:3000 +curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash: "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D }' localhost:3001 printf '\nValidate good hashes\n' -curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "4a8fb104eebbd336", "hash": "8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "326f310d629a8a98", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "6866c1ac3831a891", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3000 +curl -d '{ "action": "work_validate", "work": "47c83266398728cf", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "4a8fb104eebbd336", "hash": "8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "326f310d629a8a98", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "6866c1ac3831a891", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3001 printf '\nValidate bad hashes\n' -curl -d '{ "action": "work_validate", "work": "0000000000000000", "hash": "0000000000000000000000000000000000000000000000000000000000000000" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "ae238556213c3624", "hash": "BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6", "difficulty": "ffffffff00000000" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "29a9ae0236990e2e", "hash": "32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "7d903b18d03f9820", "hash": "39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150", "difficulty": "fffffe0000000000" }' localhost:3000 -curl -d '{ "action": "work_validate", "work": "e45835c3b291c3d1", "hash": "9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F", "difficulty": "ffffffff00000000" }' localhost:3000 +curl -d '{ "action": "work_validate", "work": "0000000000000000", "hash": "0000000000000000000000000000000000000000000000000000000000000000" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "c5d5d6f7c5d6ccd1", "hash": "BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "ae238556213c3624", "hash": "BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6", "difficulty": "ffffffff00000000" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "29a9ae0236990e2e", "hash": "32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "7d903b18d03f9820", "hash": "39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150", "difficulty": "fffffe0000000000" }' localhost:3001 +curl -d '{ "action": "work_validate", "work": "e45835c3b291c3d1", "hash": "9DCD89E2B92FD59D7358C2C2E4C225DF94C88E187B27882F50FEFC3760D3994F", "difficulty": "ffffffff00000000" }' localhost:3001 printf '\nGenerate\n' -curl -d '{ "action": "work_generate", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3000 -curl -d '{ "action": "work_generate", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3000 -curl -d '{ "action": "work_generate", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3000 +curl -d '{ "action": "work_generate", "hash": "92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D" }' localhost:3001 +curl -d '{ "action": "work_generate", "hash": "204076E3364D16A018754FF67D418AB2FBEB38799FF9A29A1D5F9E34F16BEEEA", "difficulty": "ffffffff00000000" }' localhost:3001 +curl -d '{ "action": "work_generate", "hash": "7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090", "difficulty": "fffffe0000000000" }' localhost:3001 kill $(cat "$HOME"/.nano-pow/server.pid) && rm "$HOME"/.nano-pow/server.pid -- 2.47.3