nano-pow 4.0.6 → 4.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -26
- package/dist/bin/cli.js +87 -112
- package/dist/bin/nano-pow.sh +1 -1
- package/dist/bin/server.js +150 -166
- package/dist/main.min.js +2 -6
- package/dist/types.d.ts +11 -3
- package/docs/nano-pow.1 +78 -7
- package/package.json +1 -1
- package/docs/index.js +0 -38
package/README.md
CHANGED
|
@@ -17,8 +17,10 @@ https://docs.nano.org/integration-guides/work-generation/#work-calculation-detai
|
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
```console
|
|
20
|
-
npm i nano-pow
|
|
20
|
+
$ npm i nano-pow
|
|
21
21
|
```
|
|
22
|
+
NanoPow can also be installed globally to add the `nano-pow` command to your
|
|
23
|
+
environment. To learn more, see [#Executables](#executables).
|
|
22
24
|
|
|
23
25
|
## Usage
|
|
24
26
|
### Import
|
|
@@ -46,17 +48,17 @@ Use it directly on a webpage with a script module:
|
|
|
46
48
|
<script type="module">
|
|
47
49
|
(async () => {
|
|
48
50
|
const { NanoPow } = await import('https://cdn.jsdelivr.net/npm/nano-pow@latest')
|
|
49
|
-
const work = await NanoPow.
|
|
51
|
+
const { work } = await NanoPow.work_generate(some_hash)
|
|
50
52
|
console.log(work)
|
|
51
53
|
})()
|
|
52
54
|
</script>
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
###
|
|
57
|
+
### Generate
|
|
56
58
|
```javascript
|
|
57
59
|
// `hash` is a 64-char hex string
|
|
58
60
|
const hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
|
59
|
-
const work = await NanoPow.
|
|
61
|
+
const { work } = await NanoPow.work_generate(hash)
|
|
60
62
|
// Result is a 16-char hex string
|
|
61
63
|
```
|
|
62
64
|
|
|
@@ -66,29 +68,112 @@ const work = await NanoPow.search(hash)
|
|
|
66
68
|
const work = 'fedcba0987654321'
|
|
67
69
|
// `hash` is a 64-char hex string
|
|
68
70
|
const hash = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
|
69
|
-
const
|
|
71
|
+
const { valid } = await NanoPow.work_validate(work, hash)
|
|
70
72
|
// Result is a boolean
|
|
71
73
|
```
|
|
72
74
|
|
|
73
75
|
### Options
|
|
74
76
|
```javascript
|
|
75
77
|
const options = {
|
|
76
|
-
// default
|
|
78
|
+
// default 0xFFFFFFF800000000 for send/change blocks
|
|
77
79
|
threshold: number,
|
|
78
80
|
// default 8, valid range 1-32
|
|
79
81
|
effort: number,
|
|
80
82
|
// default false
|
|
81
83
|
debug: true
|
|
82
84
|
}
|
|
83
|
-
const work = await NanoPow.
|
|
85
|
+
const { work } = await NanoPow.work_generate(hash, options)
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
## Executables
|
|
87
89
|
NanoPow can be installed globally and executed from the command line. This is
|
|
88
90
|
useful for systems without a graphical interface.
|
|
89
91
|
```console
|
|
90
92
|
$ npm -g i nano-pow
|
|
91
|
-
$ nano-pow --help
|
|
93
|
+
$ nano-pow --help # view abbreviated CLI help
|
|
94
|
+
$ man nano-pow # view full manual
|
|
95
|
+
```
|
|
96
|
+
Ensure you have proper permissions on your
|
|
97
|
+
[npm `prefix`](https://docs.npmjs.com/cli/v11/commands/npm-prefix) directory and
|
|
98
|
+
have configured your `PATH` accordingly.
|
|
99
|
+
|
|
100
|
+
For example, this adds a user-specific directory for local binaries to `PATH`
|
|
101
|
+
upon login and configures `npm prefix` to use it for global installations:
|
|
102
|
+
```console
|
|
103
|
+
$ echo 'PATH="$HOME/.local/bin:$PATH"' >> .bashrc
|
|
104
|
+
$ npm config set prefix="$HOME/.local"
|
|
105
|
+
```
|
|
106
|
+
### Command Line
|
|
107
|
+
NanoPow provides a shell command—`nano-pow`—to accomodate systems
|
|
108
|
+
without a graphical user interface. It launches a headless Chrome browser using
|
|
109
|
+
`puppeteer` to access the required WebGPU or WebGL APIs. Use the `--global` flag
|
|
110
|
+
when installing to add the executable script to your system.
|
|
111
|
+
```console
|
|
112
|
+
$ npm i -g nano-pow
|
|
113
|
+
```
|
|
114
|
+
Some examples are provided below, and for full documentation, read the manual
|
|
115
|
+
with `man nano-pow`.
|
|
116
|
+
|
|
117
|
+
```console
|
|
118
|
+
$ # Generate a work value using default settings and debugging output enabled.
|
|
119
|
+
$ nano-pow --debug 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
120
|
+
```
|
|
121
|
+
```console
|
|
122
|
+
$ # Generate work using customized behavior with options.
|
|
123
|
+
$ nano-pow --effort 32 --threshold FFFFFFC000000000 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
124
|
+
```
|
|
125
|
+
```console
|
|
126
|
+
$ # Validate an existing work nonce against a blockhash.
|
|
127
|
+
nano-pow --validate fedcba9876543210 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
128
|
+
```
|
|
129
|
+
```console
|
|
130
|
+
$ # Process blockhashes in batches to reduce the initial startup overhead.
|
|
131
|
+
$ nano-pow 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef [...]
|
|
132
|
+
$ # OR
|
|
133
|
+
nano-pow $(cat /path/to/hashes/file)
|
|
134
|
+
$ # OR
|
|
135
|
+
$ cat /path/to/hashes/file | nano-pow
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Server
|
|
139
|
+
NanoPow also provides a basic work server similar to the one included in the
|
|
140
|
+
official Nano node software. The installed command will launch the server in a
|
|
141
|
+
detached process, and you can also start it yourself to customize behavior by
|
|
142
|
+
executing the server script directly.
|
|
143
|
+
|
|
144
|
+
`PORT` can be passed as an environment variable and defaults to 3000 if not
|
|
145
|
+
specified.
|
|
146
|
+
|
|
147
|
+
`NANO_POW_EFFORT` can also be passed as an environment variable to increase
|
|
148
|
+
or decrease the demand on the GPU and defaults to 8 if not specified.
|
|
149
|
+
|
|
150
|
+
```console
|
|
151
|
+
$ # Launch the server and detach from the current session
|
|
152
|
+
$ PORT=8080 nano-pow --server
|
|
153
|
+
$ # View process ID for "NanoPow Server"
|
|
154
|
+
$ cat ~/.nano-pow/server.pid
|
|
155
|
+
$ # Display list of server logs
|
|
156
|
+
$ ls ~/.nano-pow/logs/
|
|
157
|
+
$ # Find process ID manually
|
|
158
|
+
$ ps aux | grep NanoPow
|
|
159
|
+
```
|
|
160
|
+
Work is generated or validated by sending an HTTP `POST` request to the
|
|
161
|
+
configured hostname or IP address of the machine. Some basic help is available
|
|
162
|
+
via `GET` request.
|
|
163
|
+
```console
|
|
164
|
+
$ # Generate a work value
|
|
165
|
+
$ curl -d '{
|
|
166
|
+
"action": "work_generate",
|
|
167
|
+
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
168
|
+
}' localhost:3000
|
|
169
|
+
```
|
|
170
|
+
```console
|
|
171
|
+
$ # Validate a work value
|
|
172
|
+
$ curl -d '{
|
|
173
|
+
"action": "work_validate",
|
|
174
|
+
"work": "e45835c3b291c3d1",
|
|
175
|
+
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
176
|
+
}' localhost:3000
|
|
92
177
|
```
|
|
93
178
|
|
|
94
179
|
## Notes
|
|
@@ -106,21 +191,16 @@ for all other accounts.
|
|
|
106
191
|
* 𝘵𝘩𝘳𝘦𝘴𝘩𝘰𝘭𝘥 is 0xFFFFFFF800000000 for send/change blocks and 0xFFFFFE0000000000
|
|
107
192
|
for receive/open/epoch blocks.
|
|
108
193
|
|
|
109
|
-
The threshold is implemented in code as only the first 32 bits due to WGSL only
|
|
110
|
-
supporting u32 integer types, but the result is the same. For example, if
|
|
111
|
-
checking whether a two-digit number xx > 55, and it is known that the first
|
|
112
|
-
digit is 6, it is also automatically known that xx is greater than 55. The
|
|
113
|
-
default threshold used is the send/change difficulty. Any other threshold can be
|
|
114
|
-
specified in practice.
|
|
115
|
-
|
|
116
194
|
The BLAKE2b implementation has been optimized to the extreme for this package
|
|
117
|
-
due to the very narrow use case to which it is applied. The compute shader
|
|
118
|
-
consequently immense, but the goal is to squeeze
|
|
119
|
-
performance out of it.
|
|
195
|
+
due to the very narrow use case to which it is applied. The compute shader used
|
|
196
|
+
by the WebGPU implementation is consequently immense, but the goal is to squeeze
|
|
197
|
+
every last bit of speed and performance out of it.
|
|
120
198
|
|
|
121
199
|
## Tests
|
|
122
|
-
|
|
123
|
-
|
|
200
|
+
A few basic tests are availabe in the source repository.
|
|
201
|
+
* `test/index.html` in the source repository contains a web interface to change
|
|
202
|
+
execution options and compare results.
|
|
203
|
+
* `test/script.sh` starts the `nano-pow` server and sends some basic requests.
|
|
124
204
|
|
|
125
205
|
## Building
|
|
126
206
|
1. Clone source
|
|
@@ -128,11 +208,10 @@ speed of this tool. Feel free to check out how your system fares.
|
|
|
128
208
|
1. Install dev dependencies
|
|
129
209
|
1. Compile, minify, and bundle
|
|
130
210
|
|
|
131
|
-
```
|
|
132
|
-
git clone https://zoso.dev/nano-pow.git
|
|
133
|
-
cd nano-pow
|
|
134
|
-
npm i
|
|
135
|
-
npm run build
|
|
211
|
+
```console
|
|
212
|
+
$ git clone https://zoso.dev/nano-pow.git
|
|
213
|
+
$ cd nano-pow
|
|
214
|
+
$ npm i
|
|
136
215
|
```
|
|
137
216
|
|
|
138
217
|
## Reporting Bugs
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,161 +1,136 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
3
3
|
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
-
import * as crypto from "node:crypto";
|
|
5
|
-
import * as fs from "node:fs/promises";
|
|
6
4
|
import * as readline from "node:readline/promises";
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
process.title = "NanoPow CLI";
|
|
6
|
+
process.env.NANO_POW_DEBUG = "";
|
|
7
|
+
process.env.NANO_POW_EFFORT = "";
|
|
8
|
+
process.env.NANO_POW_PORT = "3000";
|
|
9
|
+
function log(...args2) {
|
|
10
|
+
if (process.env.NANO_POW_DEBUG) console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args2);
|
|
11
|
+
}
|
|
9
12
|
const hashes = [];
|
|
10
13
|
const stdinErrors = [];
|
|
11
14
|
if (!process.stdin.isTTY) {
|
|
12
15
|
const stdin = readline.createInterface({
|
|
13
16
|
input: process.stdin
|
|
14
17
|
});
|
|
18
|
+
let i = 0;
|
|
15
19
|
for await (const line of stdin) {
|
|
20
|
+
i++;
|
|
16
21
|
if (/^[0-9A-Fa-f]{64}$/.test(line)) {
|
|
17
22
|
hashes.push(line);
|
|
18
23
|
} else {
|
|
19
|
-
stdinErrors.push(`Skipping invalid stdin input
|
|
24
|
+
stdinErrors.push(`Skipping invalid stdin input line ${i}`);
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
const args = process.argv.slice(2);
|
|
24
29
|
if (hashes.length === 0 && args.length === 0 || args.some((v) => v === "--help" || v === "-h")) {
|
|
25
|
-
console.log(
|
|
26
|
-
|
|
30
|
+
console.log(
|
|
31
|
+
`Usage: nano-pow [OPTION]... BLOCKHASH...
|
|
32
|
+
Generate work for BLOCKHASH, or multiple work values for BLOCKHASH(es)
|
|
33
|
+
BLOCKHASH is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
|
|
34
|
+
Prints the result as a Javascript object to standard output as soon as it is calculated.
|
|
35
|
+
If using --batch, results are printed only after all BLOCKHASH(es) have be processed.
|
|
36
|
+
If using --validate, results will also include validity properties.
|
|
37
|
+
|
|
38
|
+
-h, --help show this dialog
|
|
39
|
+
--debug enable additional logging output
|
|
40
|
+
|
|
41
|
+
-b, --batch process all data before returning final results as array
|
|
42
|
+
-d, --difficulty=<value> override the minimum difficulty value
|
|
43
|
+
-e, --effort=<value> increase demand on GPU processing
|
|
44
|
+
-v, --validate=<value> check an existing work value instead of searching for one
|
|
45
|
+
|
|
46
|
+
If validating a nonce, it must be a 16-character hexadecimal value.
|
|
47
|
+
Effort must be a decimal number between 1-32.
|
|
48
|
+
Difficulty must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.
|
|
49
|
+
|
|
50
|
+
Report bugs: <bug-nano-pow@zoso.dev>
|
|
51
|
+
Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
52
|
+
`
|
|
53
|
+
);
|
|
54
|
+
process.exit(0);
|
|
27
55
|
}
|
|
28
56
|
const inArgs = [];
|
|
29
57
|
while (/^[0-9A-Fa-f]{64}$/.test(args[args.length - 1] ?? "")) {
|
|
30
58
|
inArgs.unshift(args.pop());
|
|
31
59
|
}
|
|
32
60
|
hashes.push(...inArgs);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
if (hashes.length === 0) {
|
|
62
|
+
console.error("Invalid block hash input");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
let isBatch = false;
|
|
66
|
+
const body = {
|
|
67
|
+
action: "work_generate"
|
|
68
|
+
};
|
|
37
69
|
for (let i = 0; i < args.length; i++) {
|
|
38
70
|
switch (args[i]) {
|
|
39
71
|
case "--validate":
|
|
40
72
|
case "-v": {
|
|
41
73
|
if (args[i + 1] == null) throw new Error("Missing argument for work validation");
|
|
42
74
|
if (!/^[0-9A-Fa-f]{16}$/.test(args[i + 1])) throw new Error("Invalid work to validate");
|
|
43
|
-
|
|
44
|
-
work =
|
|
75
|
+
body.action = "work_validate";
|
|
76
|
+
body.work = args[i + 1];
|
|
45
77
|
break;
|
|
46
78
|
}
|
|
47
|
-
case "--
|
|
48
|
-
case "-
|
|
49
|
-
if (args[i + 1] == null) throw new Error("Missing argument for
|
|
50
|
-
if (!/^[0-9A-Fa-f]{0,
|
|
51
|
-
|
|
79
|
+
case "--difficulty":
|
|
80
|
+
case "-d": {
|
|
81
|
+
if (args[i + 1] == null) throw new Error("Missing argument for difficulty");
|
|
82
|
+
if (!/^[0-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(args[i + 1])) throw new Error("Invalid difficulty");
|
|
83
|
+
body.difficulty = args[i + 1];
|
|
52
84
|
break;
|
|
53
85
|
}
|
|
54
86
|
case "--effort":
|
|
55
87
|
case "-e": {
|
|
56
88
|
if (args[i + 1] == null) throw new Error("Missing argument for effort");
|
|
57
89
|
if (!/^[0-9]{0,2}$/.test(args[i + 1])) throw new Error("Invalid effort");
|
|
58
|
-
|
|
90
|
+
process.env.NANO_POW_EFFORT = args[i + 1];
|
|
59
91
|
break;
|
|
60
92
|
}
|
|
61
|
-
case "--debug":
|
|
62
|
-
|
|
63
|
-
options["debug"] = true;
|
|
93
|
+
case "--debug": {
|
|
94
|
+
process.env.NANO_POW_DEBUG = "true";
|
|
64
95
|
break;
|
|
65
96
|
}
|
|
66
|
-
case "--
|
|
67
|
-
case "-
|
|
68
|
-
|
|
97
|
+
case "--batch":
|
|
98
|
+
case "-b": {
|
|
99
|
+
isBatch = true;
|
|
69
100
|
break;
|
|
70
101
|
}
|
|
71
102
|
}
|
|
72
103
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for (const stdinErr of stdinErrors) {
|
|
77
|
-
console.warn(stdinErr);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (hashes.length === 0) {
|
|
81
|
-
console.error("Invalid block hash input");
|
|
82
|
-
process.exit(1);
|
|
104
|
+
log("CLI args:", ...args);
|
|
105
|
+
for (const stdinErr of stdinErrors) {
|
|
106
|
+
log(stdinErr);
|
|
83
107
|
}
|
|
84
|
-
(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const inject = `
|
|
105
|
-
${NanoPow}
|
|
106
|
-
window.results = []
|
|
107
|
-
const hashes = ["${hashes.join('","')}"]
|
|
108
|
-
for (const hash of hashes) {
|
|
109
|
-
try {
|
|
110
|
-
const result = await NanoPow.${fn}(${work}hash, ${JSON.stringify(options)})
|
|
111
|
-
window.results.push(result)
|
|
112
|
-
console.log(\`cli \${JSON.stringify(result, null, 4)}\`)
|
|
113
|
-
} catch (err) {
|
|
114
|
-
console.error(\`cli \${err}\`)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
console.log('cli exit')
|
|
118
|
-
`;
|
|
119
|
-
const hash = await crypto.subtle.digest("SHA-256", Buffer.from(inject, "utf-8"));
|
|
120
|
-
const src = `sha256-${Buffer.from(hash).toString("base64")}`;
|
|
121
|
-
let start = performance.now();
|
|
122
|
-
page.on("console", async (msg) => {
|
|
123
|
-
const output = msg.text().split(/^cli /);
|
|
124
|
-
if (output[0] === "") {
|
|
125
|
-
if (output[1] === "exit") {
|
|
126
|
-
if (isJson) {
|
|
127
|
-
const results = await page.evaluate(() => {
|
|
128
|
-
return window.results;
|
|
129
|
-
});
|
|
130
|
-
console.log(JSON.stringify(results, null, 4));
|
|
131
|
-
}
|
|
132
|
-
const end = performance.now();
|
|
133
|
-
if (options["debug"]) console.log(end - start, "ms total |", (end - start) / hashes.length, "ms avg");
|
|
134
|
-
await browser.close();
|
|
135
|
-
} else if (!isJson) {
|
|
136
|
-
try {
|
|
137
|
-
console.log(JSON.parse(output[1]));
|
|
138
|
-
} catch (err) {
|
|
139
|
-
console.log(output[1]);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} else if (options["debug"]) {
|
|
143
|
-
try {
|
|
144
|
-
console.log(JSON.parse(msg.text()));
|
|
145
|
-
} catch (err) {
|
|
146
|
-
console.log(msg.text());
|
|
147
|
-
}
|
|
108
|
+
log("Starting NanoPow CLI");
|
|
109
|
+
await import("./server.js");
|
|
110
|
+
const results = [];
|
|
111
|
+
const start = performance.now();
|
|
112
|
+
const aborter = new AbortController();
|
|
113
|
+
for (const hash of hashes) {
|
|
114
|
+
try {
|
|
115
|
+
body.hash = hash;
|
|
116
|
+
const kill = setTimeout(aborter.abort, 5e3);
|
|
117
|
+
const response = await fetch(`http://localhost:${process.env.NANO_POW_PORT}`, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
body: JSON.stringify(body),
|
|
120
|
+
signal: aborter.signal
|
|
121
|
+
});
|
|
122
|
+
clearTimeout(kill);
|
|
123
|
+
const result = await response.json();
|
|
124
|
+
if (isBatch) {
|
|
125
|
+
results.push(result);
|
|
126
|
+
} else {
|
|
127
|
+
console.log(result);
|
|
148
128
|
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
</html>
|
|
158
|
-
`);
|
|
159
|
-
await fs.unlink(`${dir}/cli.html`);
|
|
160
|
-
if (options["debug"]) console.log("Puppeteer initialized");
|
|
161
|
-
})();
|
|
129
|
+
} catch (err) {
|
|
130
|
+
log(err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const end = performance.now();
|
|
134
|
+
if (isBatch) console.log(results);
|
|
135
|
+
log(end - start, "ms total |", (end - start) / hashes.length, "ms avg");
|
|
136
|
+
process.exit(0);
|
package/dist/bin/nano-pow.sh
CHANGED
|
@@ -10,7 +10,7 @@ NANO_POW_LOGS="$NANO_POW_HOME"/logs;
|
|
|
10
10
|
mkdir -p "$NANO_POW_LOGS";
|
|
11
11
|
if [ "$1" = '--server' ]; then
|
|
12
12
|
shift;
|
|
13
|
-
node "$SCRIPT_DIR"/server.js
|
|
13
|
+
node "$SCRIPT_DIR"/server.js > "$NANO_POW_LOGS"/nano-pow-server-$(date +%s).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid;
|
|
14
14
|
else
|
|
15
15
|
node "$SCRIPT_DIR"/cli.js "$@";
|
|
16
16
|
fi;
|
package/dist/bin/server.js
CHANGED
|
@@ -1,181 +1,165 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
3
3
|
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { launch } from "puppeteer";
|
|
6
|
+
import { lookup } from "node:dns/promises";
|
|
7
7
|
import * as http from "node:http";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const NanoPow = await fs.readFile(new URL("../main.min.js", import.meta.url), "utf-8");
|
|
8
|
+
import { hostname } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
process.title = "NanoPow Server";
|
|
11
|
+
const MAX_REQUEST_SIZE = 1024;
|
|
12
|
+
const MAX_BODY_SIZE = 158;
|
|
13
|
+
const DEBUG = !!(process.env.NANO_POW_DEBUG || false);
|
|
14
|
+
const EFFORT = +(process.env.NANO_POW_EFFORT || 8);
|
|
15
|
+
const PORT = +(process.env.NANO_POW_PORT || 3e3);
|
|
17
16
|
let browser;
|
|
18
17
|
let page;
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
27
|
-
res.end(JSON.stringify({ error: "Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF." }));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
18
|
+
function log(...args) {
|
|
19
|
+
if (DEBUG) console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args);
|
|
20
|
+
}
|
|
21
|
+
async function respond(res, data) {
|
|
22
|
+
let statusCode = 500;
|
|
23
|
+
let headers = { "Content-Type": "application/json" };
|
|
24
|
+
let response = "work_validate failed";
|
|
30
25
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
const datastring = Buffer.concat(data).toString().replace(/\s+/g, "");
|
|
27
|
+
if (Buffer.byteLength(datastring) > MAX_BODY_SIZE) {
|
|
28
|
+
throw new Error("Invalid data.");
|
|
29
|
+
}
|
|
30
|
+
const { action, hash: hash2, work, difficulty } = JSON.parse(datastring);
|
|
31
|
+
if (action !== "work_generate" && action !== "work_validate") {
|
|
32
|
+
throw new Error("Invalid action. Must be work_generate or work_validate.");
|
|
33
|
+
}
|
|
34
|
+
if (!/^[0-9A-Fa-f]{64}$/.test(hash2 ?? "")) {
|
|
35
|
+
throw new Error("Invalid hash. Must be a 64-character hex string.");
|
|
36
|
+
}
|
|
37
|
+
if (difficulty && !/^[1-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(difficulty)) {
|
|
38
|
+
throw new Error("Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.");
|
|
39
|
+
}
|
|
40
|
+
if (action === "work_validate" && !/^[0-9A-Fa-f]{16}$/.test(work ?? "")) {
|
|
41
|
+
throw new Error("Invalid work. Must be a 16-character hex string.");
|
|
42
|
+
}
|
|
43
|
+
const options = {
|
|
44
|
+
debug: DEBUG,
|
|
45
|
+
effort: EFFORT,
|
|
46
|
+
threshold: difficulty
|
|
47
|
+
};
|
|
48
|
+
const args = [];
|
|
49
|
+
if (work) args.push(work);
|
|
50
|
+
args.push(hash2);
|
|
51
|
+
args.push(options);
|
|
52
|
+
response = JSON.stringify(await page.evaluate(async (action2, args2) => {
|
|
53
|
+
if (window.NanoPow == null) throw new Error("NanoPow not found");
|
|
54
|
+
return await window.NanoPow[action2](...args2);
|
|
55
|
+
}, action, args));
|
|
56
|
+
statusCode = 200;
|
|
40
57
|
} catch (err) {
|
|
41
|
-
log(
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
log(err);
|
|
59
|
+
statusCode = 400;
|
|
60
|
+
} finally {
|
|
61
|
+
res.writeHead(statusCode, headers).end(response);
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
const server = http.createServer((req, res) => {
|
|
65
|
+
let data = [];
|
|
66
|
+
let reqSize = 0;
|
|
67
|
+
if (req.method === "POST") {
|
|
68
|
+
req.on("data", (chunk) => {
|
|
69
|
+
reqSize += chunk.byteLength;
|
|
70
|
+
if (reqSize > MAX_REQUEST_SIZE) {
|
|
71
|
+
res.writeHead(413, { "Content-Type": "text/plain" });
|
|
72
|
+
res.end("Content Too Large");
|
|
73
|
+
req.socket.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
data.push(chunk);
|
|
77
|
+
});
|
|
78
|
+
req.on("end", async () => {
|
|
79
|
+
if (!req.socket.destroyed) {
|
|
80
|
+
await respond(res, data);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
85
|
+
res.end(
|
|
86
|
+
`Usage: Send POST request to server URL to generate or validate Nano proof-of-work
|
|
87
|
+
|
|
88
|
+
Generate work for a BLOCKHASH with an optional DIFFICULTY:
|
|
89
|
+
curl -d '{ action: "work_generate", hash: BLOCKHASH, difficulty?: DIFFICULTY }'
|
|
90
|
+
|
|
91
|
+
Validate WORK previously calculated for a BLOCKHASH with an optional DIFFICULTY:
|
|
92
|
+
curl -d '{ action: "work_validate", work: WORK, hash: BLOCKHASH, difficulty?: DIFFICULTY }'
|
|
93
|
+
|
|
94
|
+
BLOCKHASH is a 64-character hexadecimal string.
|
|
95
|
+
WORK is 16-character hexadecimal string.
|
|
96
|
+
DIFFICULTY is a 16-character hexadecimal string (default: FFFFFFF800000000)
|
|
97
|
+
|
|
98
|
+
Report bugs: <bug-nano-pow@zoso.dev>
|
|
99
|
+
Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
100
|
+
`
|
|
101
|
+
);
|
|
61
102
|
}
|
|
103
|
+
});
|
|
104
|
+
server.on("error", (e) => {
|
|
105
|
+
log("Server error", e);
|
|
62
106
|
try {
|
|
63
|
-
|
|
64
|
-
const options = {
|
|
65
|
-
debug: true
|
|
66
|
-
};
|
|
67
|
-
if (args.difficulty) options.threshold = BigInt(`0x${args.difficulty}`);
|
|
68
|
-
return await window.NanoPow.work_validate(args.work, args.hash, options);
|
|
69
|
-
}, json);
|
|
70
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
71
|
-
res.end(JSON.stringify(result));
|
|
107
|
+
shutdown();
|
|
72
108
|
} catch (err) {
|
|
73
|
-
log("
|
|
74
|
-
|
|
75
|
-
res.end(JSON.stringify({ error: "work_validate failed" }));
|
|
109
|
+
log("Failed to shut down", err);
|
|
110
|
+
process.exit(1);
|
|
76
111
|
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
page = await browser.newPage();
|
|
90
|
-
page.on("console", (msg) => {
|
|
91
|
-
log(msg.text());
|
|
92
|
-
});
|
|
93
|
-
const path = new URL(import.meta.url).pathname;
|
|
94
|
-
const dir = path.slice(0, path.lastIndexOf("/"));
|
|
95
|
-
await fs.writeFile(`${dir}/server.html`, "");
|
|
96
|
-
await page.goto(import.meta.resolve("./server.html"));
|
|
97
|
-
await page.waitForFunction(async () => {
|
|
98
|
-
return await navigator.gpu.requestAdapter();
|
|
99
|
-
});
|
|
100
|
-
const inject = `${NanoPow};window.NanoPow=NanoPow;`;
|
|
101
|
-
const hash = await crypto.subtle.digest("SHA-256", Buffer.from(inject, "utf-8"));
|
|
102
|
-
const src = `sha256-${Buffer.from(hash).toString("base64")}`;
|
|
103
|
-
await page.setContent(`
|
|
104
|
-
<!DOCTYPE html>
|
|
105
|
-
<head>
|
|
106
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${src}';">
|
|
107
|
-
<script type="module">${inject}</script>
|
|
108
|
-
</head>
|
|
109
|
-
</html>
|
|
110
|
-
`);
|
|
111
|
-
await fs.unlink(`${dir}/server.html`);
|
|
112
|
-
log("Puppeteer initialized");
|
|
113
|
-
const server = http.createServer(async (req, res) => {
|
|
114
|
-
let data = [];
|
|
115
|
-
if (req.method === "POST") {
|
|
116
|
-
req.on("data", (chunk) => {
|
|
117
|
-
data.push(chunk);
|
|
118
|
-
});
|
|
119
|
-
req.on("end", async () => {
|
|
120
|
-
let json;
|
|
121
|
-
try {
|
|
122
|
-
json = JSON.parse(Buffer.concat(data).toString());
|
|
123
|
-
} catch (err) {
|
|
124
|
-
log("JSON.parse error:", err);
|
|
125
|
-
log("Failed JSON:", json);
|
|
126
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
127
|
-
res.end(JSON.stringify({ error: "Invalid data." }));
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
switch (json.action) {
|
|
131
|
-
case "work_generate": {
|
|
132
|
-
await work_generate(res, json);
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
case "work_validate": {
|
|
136
|
-
await work_validate(res, json);
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
default: {
|
|
140
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
141
|
-
res.end(JSON.stringify({ error: `Invalid data.` }));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
} else {
|
|
147
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
148
|
-
res.end(serverHelp);
|
|
149
|
-
}
|
|
112
|
+
});
|
|
113
|
+
function shutdown() {
|
|
114
|
+
log("Shutdown signal received");
|
|
115
|
+
const kill = setTimeout(() => {
|
|
116
|
+
log("Server unresponsive, forcefully stopped");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}, 1e4);
|
|
119
|
+
server.close(() => {
|
|
120
|
+
clearTimeout(kill);
|
|
121
|
+
log("Server stopped");
|
|
122
|
+
process.exit(0);
|
|
150
123
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
124
|
+
}
|
|
125
|
+
process.on("SIGINT", shutdown);
|
|
126
|
+
process.on("SIGTERM", shutdown);
|
|
127
|
+
log("Starting NanoPow work server");
|
|
128
|
+
const NanoPow = await readFile(new URL("../main.min.js", import.meta.url), "utf-8");
|
|
129
|
+
browser = await launch({
|
|
130
|
+
headless: true,
|
|
131
|
+
args: [
|
|
132
|
+
"--headless=new",
|
|
133
|
+
"--use-angle=vulkan",
|
|
134
|
+
"--enable-features=Vulkan",
|
|
135
|
+
"--disable-vulkan-surface",
|
|
136
|
+
"--enable-unsafe-webgpu"
|
|
137
|
+
]
|
|
138
|
+
});
|
|
139
|
+
page = await browser.newPage();
|
|
140
|
+
page.on("console", (msg) => log(msg.text()));
|
|
141
|
+
const path = new URL(import.meta.url).pathname;
|
|
142
|
+
const dir = path.slice(0, path.lastIndexOf("/"));
|
|
143
|
+
const filename = join(dir, `${process.pid}.html`);
|
|
144
|
+
await writeFile(filename, "");
|
|
145
|
+
await page.goto(import.meta.resolve(filename));
|
|
146
|
+
await page.waitForFunction(async () => {
|
|
147
|
+
return await navigator["gpu"].requestAdapter();
|
|
148
|
+
});
|
|
149
|
+
const src = `${NanoPow};window.NanoPow=NanoPow;`;
|
|
150
|
+
const hash = await crypto.subtle.digest("SHA-256", Buffer.from(src));
|
|
151
|
+
const enc = `sha256-${Buffer.from(hash).toString("base64")}`;
|
|
152
|
+
await page.setContent(`
|
|
153
|
+
<!DOCTYPE html>
|
|
154
|
+
<head>
|
|
155
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${enc}';">
|
|
156
|
+
<script type="module">${src}</script>
|
|
157
|
+
</head>
|
|
158
|
+
</html>
|
|
159
|
+
`);
|
|
160
|
+
await unlink(filename);
|
|
161
|
+
log("Puppeteer initialized");
|
|
162
|
+
server.listen(PORT, async () => {
|
|
163
|
+
const ip = await lookup(hostname(), { family: 4 });
|
|
164
|
+
log(`Server process ${process.pid} running at ${ip.address}:${PORT}/`);
|
|
165
|
+
});
|
package/dist/main.min.js
CHANGED
|
@@ -51,10 +51,6 @@ var NanoPowGl = class _NanoPowGl {
|
|
|
51
51
|
static #cores = Math.max(1, Math.floor(navigator.hardwareConcurrency));
|
|
52
52
|
static #WORKLOAD = 256 * this.#cores;
|
|
53
53
|
static #canvas;
|
|
54
|
-
/** Drawing buffer size in pixels. */
|
|
55
|
-
static get size() {
|
|
56
|
-
return (this.#gl?.drawingBufferWidth ?? 0) * (this.#gl?.drawingBufferHeight ?? 0);
|
|
57
|
-
}
|
|
58
54
|
static #gl;
|
|
59
55
|
static #drawProgram;
|
|
60
56
|
static #downsampleProgram;
|
|
@@ -187,14 +183,14 @@ var NanoPowGl = class _NanoPowGl {
|
|
|
187
183
|
this.#gl.uniformBlockBinding(this.#drawProgram, this.#gl.getUniformBlockIndex(this.#drawProgram, "WORK"), 1);
|
|
188
184
|
this.#gl.bindBuffer(this.#gl.UNIFORM_BUFFER, null);
|
|
189
185
|
this.#query = this.#gl.createQuery();
|
|
190
|
-
this.#pixels = new Uint32Array(this.
|
|
186
|
+
this.#pixels = new Uint32Array(this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight * 4);
|
|
191
187
|
} catch (err) {
|
|
192
188
|
throw new Error("WebGL initialization failed.", { cause: err });
|
|
193
189
|
} finally {
|
|
194
190
|
this.#busy = false;
|
|
195
191
|
}
|
|
196
192
|
this.#isInitialized = true;
|
|
197
|
-
console.log(`NanoPow WebGL initialized
|
|
193
|
+
console.log(`NanoPow WebGL initialized. Maximum nonces checked per frame: ${this.#gl.drawingBufferWidth * this.#gl.drawingBufferHeight}`);
|
|
198
194
|
}
|
|
199
195
|
/**
|
|
200
196
|
* On WebGL context loss, attempts to clear all program variables and then
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
2
2
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
NanoPow: typeof NanoPow
|
|
7
|
+
}
|
|
8
|
+
}
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Used by work server for inbound requests to `work_generate`.
|
|
@@ -11,6 +15,7 @@ import '@webgpu/types'
|
|
|
11
15
|
* @param {string} [difficulty=FFFFFFF800000000] - Minimum threshold for a nonce to be valid
|
|
12
16
|
*/
|
|
13
17
|
type WorkGenerateRequest = {
|
|
18
|
+
[key: string]: string | undefined
|
|
14
19
|
action: 'work_generate'
|
|
15
20
|
hash: string
|
|
16
21
|
difficulty?: string
|
|
@@ -24,6 +29,7 @@ type WorkGenerateRequest = {
|
|
|
24
29
|
* @param {string} difficulty - BLAKE2b hash result which was compared to specified minimum threshold
|
|
25
30
|
*/
|
|
26
31
|
type WorkGenerateResponse = {
|
|
32
|
+
[key: string]: string
|
|
27
33
|
hash: string
|
|
28
34
|
work: string
|
|
29
35
|
difficulty: string
|
|
@@ -38,6 +44,7 @@ type WorkGenerateResponse = {
|
|
|
38
44
|
* @param {string} [difficulty=FFFFFFF800000000] - Minimum threshold for a nonce to be valid
|
|
39
45
|
*/
|
|
40
46
|
type WorkValidateRequest = {
|
|
47
|
+
[key: string]: string | undefined
|
|
41
48
|
action: 'work_validate'
|
|
42
49
|
hash: string
|
|
43
50
|
work: string
|
|
@@ -55,6 +62,7 @@ type WorkValidateRequest = {
|
|
|
55
62
|
* @param {string} valid_receive - 1 for true if nonce is valid for receive blocks, else 0 for false
|
|
56
63
|
*/
|
|
57
64
|
type WorkValidateResponse = {
|
|
65
|
+
[key: string]: string | undefined
|
|
58
66
|
hash: string
|
|
59
67
|
work: string
|
|
60
68
|
difficulty: string
|
|
@@ -103,9 +111,8 @@ export type NanoPowOptions = {
|
|
|
103
111
|
* Nano proof-of-work using WebGL 2.0.
|
|
104
112
|
*/
|
|
105
113
|
export declare class NanoPowGl {
|
|
114
|
+
static [key: string]: (...args: any[]) => any
|
|
106
115
|
#private
|
|
107
|
-
/** Drawing buffer width in pixels. */
|
|
108
|
-
static get size (): number
|
|
109
116
|
/**
|
|
110
117
|
* Constructs canvas, gets WebGL context, initializes buffers, and compiles
|
|
111
118
|
* shaders.
|
|
@@ -153,6 +160,7 @@ export declare class NanoPowGl {
|
|
|
153
160
|
*/
|
|
154
161
|
export declare class NanoPowGpu {
|
|
155
162
|
#private
|
|
163
|
+
static [key: string]: (...args: any[]) => any
|
|
156
164
|
static init (): Promise<void>
|
|
157
165
|
static setup (): void
|
|
158
166
|
static reset (): void
|
package/docs/nano-pow.1
CHANGED
|
@@ -12,12 +12,19 @@ nano-pow \- proof-of-work generation and validation for Nano cryptocurrency
|
|
|
12
12
|
.SH DESCRIPTION
|
|
13
13
|
Generate work for \fIBLOCKHASH\fR, or multiple work values for \fIBLOCKHASH\fR(es).
|
|
14
14
|
.PP
|
|
15
|
+
If the \fI--server\fR option is provided as the first argument, all other options are ignored and the NanoPow server is started.
|
|
16
|
+
.PP
|
|
15
17
|
\fIBLOCKHASH\fR is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
|
|
16
18
|
.PP
|
|
17
|
-
Prints a 16-character hexadecimal work value
|
|
19
|
+
Prints a 16-character hexadecimal work value, the BLAKE2b result, and the originating hash to standard output as a Javascript object.
|
|
20
|
+
.PP
|
|
21
|
+
If \fB--validate\fR is used, the original work value is returned instead along with validation flags.
|
|
18
22
|
|
|
19
23
|
.SH OPTIONS
|
|
20
24
|
.TP
|
|
25
|
+
\fB--server\fR
|
|
26
|
+
Start work server (see SERVER below). Must be the first argument in order to be recognized.
|
|
27
|
+
.TP
|
|
21
28
|
\fB\-h\fR, \fB\-\-help\fR
|
|
22
29
|
Show this help dialog and exit.
|
|
23
30
|
.TP
|
|
@@ -25,20 +32,64 @@ Show this help dialog and exit.
|
|
|
25
32
|
Enable additional logging output.
|
|
26
33
|
.TP
|
|
27
34
|
\fB\-j\fR, \fB\-\-json\fR
|
|
28
|
-
Format output as JSON.
|
|
35
|
+
Format final output of all hashes as a JSON array of stringified values instead of incrementally returning Javascript objects for each result.
|
|
29
36
|
.TP
|
|
30
37
|
\fB\-e\fR, \fB\-\-effort\fR=\fIEFFORT\fR
|
|
31
38
|
Increase demand on GPU processing. Must be between 1 and 32 inclusive.
|
|
32
39
|
.TP
|
|
33
40
|
\fB\-t\fR, \fB\-\-threshold\fR=\fITHRESHOLD\fR
|
|
34
|
-
Override the minimum threshold value. Higher values increase difficulty. Must be a hexadecimal string between
|
|
41
|
+
Override the minimum threshold value. Higher values increase difficulty. Must be a hexadecimal string between 1 and FFFFFFFFFFFFFFFF inclusive.
|
|
35
42
|
.TP
|
|
36
43
|
\fB\-v\fR, \fB\-\-validate\fR=\fIWORK\fR
|
|
37
|
-
Check an existing work value instead of searching for one.
|
|
44
|
+
Check an existing work value instead of searching for one. If you pass multiple blockhashes, the work value will be validated against each one.
|
|
45
|
+
|
|
46
|
+
.SH SERVER
|
|
47
|
+
Calling \fBnano-pow\fR with the \fI--server\fR option will start the NanoPow work server in a detached process.
|
|
48
|
+
.PP
|
|
49
|
+
It provides work generation and validation via HTTP requests in a similar, but not identical, fashion as the official Nano node.
|
|
50
|
+
.PP
|
|
51
|
+
More specifically, it does not support the \fIuse_peers\fR, \fImultiplier\fR, \fIaccount\fR, \fIversion\fR, \fIblock\fR, and \fIjson_block\fR options.
|
|
52
|
+
.PP
|
|
53
|
+
By default, the server listens on port 3000. To use a different port, set the \fBPORT\fR environment variable before starting the server.
|
|
54
|
+
|
|
55
|
+
.PP
|
|
56
|
+
.EX
|
|
57
|
+
$ PORT=8080 nano-pow --server
|
|
58
|
+
.EE
|
|
59
|
+
|
|
60
|
+
.PP
|
|
61
|
+
The server process ID (PID) is saved to \fB~/.nano-pow/server.pid\fR. Log files are stored in \fB~/.nano-pow/logs/\fR.
|
|
62
|
+
.TP
|
|
63
|
+
To stop the server, terminate it using its PID.
|
|
64
|
+
.EX
|
|
65
|
+
$ cat ~/.nano-pow/server.pid
|
|
66
|
+
12345
|
|
67
|
+
$ kill 12345
|
|
68
|
+
.EE
|
|
69
|
+
.TP
|
|
70
|
+
Alternatively, use \fBpgrep\fR to find the PID by process name:
|
|
71
|
+
.EX
|
|
72
|
+
$ pgrep "NanoPow Server"
|
|
73
|
+
12345
|
|
74
|
+
$ kill $(pgrep "NanoPow Server")
|
|
75
|
+
.EE
|
|
76
|
+
|
|
77
|
+
.PP
|
|
78
|
+
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.
|
|
79
|
+
.PP
|
|
80
|
+
The following actions are supported:
|
|
81
|
+
.TP
|
|
82
|
+
\fBwork_generate\fR
|
|
83
|
+
Generate a work value for a given blockhash. Requires a \fBhash\fR field containing the 64-character hexadecimal blockhash.
|
|
84
|
+
.TP
|
|
85
|
+
\fBwork_validate\fR
|
|
86
|
+
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.
|
|
87
|
+
.PP
|
|
88
|
+
|
|
38
89
|
|
|
39
|
-
.SH EXAMPLES
|
|
90
|
+
.SH EXAMPLES - CLI
|
|
40
91
|
.PP
|
|
41
|
-
Search for a work nonce for a blockhash using the default threshold
|
|
92
|
+
Search for a work nonce for a blockhash using the default threshold 0xFFFFFFF800000000:
|
|
42
93
|
.EX
|
|
43
94
|
$ nano-pow \fB0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
|
|
44
95
|
.EE
|
|
@@ -46,7 +97,7 @@ $ nano-pow \fB0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\f
|
|
|
46
97
|
.PP
|
|
47
98
|
Search for a work nonce using a custom threshold and increased effort:
|
|
48
99
|
.EX
|
|
49
|
-
$ nano-pow \fB\-t
|
|
100
|
+
$ nano-pow \fB\-t fffffe0000000000 \-e 32 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
|
|
50
101
|
.EE
|
|
51
102
|
|
|
52
103
|
.PP
|
|
@@ -61,6 +112,26 @@ Validate an existing work nonce against a blockhash and show debugging output:
|
|
|
61
112
|
$ nano-pow \fB\-d \-v fedcba9876543210 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\fR
|
|
62
113
|
.EE
|
|
63
114
|
|
|
115
|
+
.SH EXAMPLES - SERVER
|
|
116
|
+
.PP
|
|
117
|
+
Generate a work value:
|
|
118
|
+
.EX
|
|
119
|
+
$ curl -d '{
|
|
120
|
+
"action": "work_generate",
|
|
121
|
+
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
122
|
+
}' localhost:3000
|
|
123
|
+
.EE
|
|
124
|
+
|
|
125
|
+
.PP
|
|
126
|
+
Validate a work value:
|
|
127
|
+
.EX
|
|
128
|
+
$ curl -d '{
|
|
129
|
+
"action": "work_validate",
|
|
130
|
+
"work": "e45835c3b291c3d1",
|
|
131
|
+
"hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
132
|
+
}' localhost:3000
|
|
133
|
+
.EE
|
|
134
|
+
|
|
64
135
|
.SH AUTHOR
|
|
65
136
|
Written by Chris Duncan.
|
|
66
137
|
|
package/package.json
CHANGED
package/docs/index.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
2
|
-
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
-
|
|
4
|
-
export const cliHelp = `Usage: nano-pow [OPTION]... BLOCKHASH...
|
|
5
|
-
Generate work for BLOCKHASH, or multiple work values for BLOCKHASH(es)
|
|
6
|
-
BLOCKHASH is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
|
|
7
|
-
Prints a 16-character hexadecimal work value to standard output. If using --validate, prints 'true' or 'false' to standard output instead.
|
|
8
|
-
|
|
9
|
-
-h, --help show this dialog
|
|
10
|
-
-d, --debug enable additional logging output
|
|
11
|
-
-j, --json format output as JSON
|
|
12
|
-
-e, --effort=<value> increase demand on GPU processing
|
|
13
|
-
-t, --threshold=<value> override the minimum threshold value
|
|
14
|
-
-v, --validate=<value> check an existing work value instead of searching for one
|
|
15
|
-
|
|
16
|
-
If validating a nonce, it must be a 16-character hexadecimal value.
|
|
17
|
-
Effort must be a decimal number between 1-32.
|
|
18
|
-
Threshold must be a hexadecimal string between 0-FFFFFFFF.
|
|
19
|
-
|
|
20
|
-
Report bugs: <bug-nano-pow@zoso.dev>
|
|
21
|
-
Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
22
|
-
`
|
|
23
|
-
|
|
24
|
-
export const serverHelp = `Usage: Send POST request to server URL to generate or validate Nano proof-of-work
|
|
25
|
-
|
|
26
|
-
Generate work for a BLOCKHASH with an optional DIFFICULTY:
|
|
27
|
-
curl -d '{ action: "work_generate", hash: BLOCKHASH, difficulty?: DIFFICULTY }'
|
|
28
|
-
|
|
29
|
-
Validate WORK previously calculated for a BLOCKHASH with an optional DIFFICULTY:
|
|
30
|
-
curl -d '{ action: "work_validate", work: WORK, hash: BLOCKHASH, difficulty?: DIFFICULTY }'
|
|
31
|
-
|
|
32
|
-
BLOCKHASH is a 64-character hexadecimal string.
|
|
33
|
-
WORK is 16-character hexadecimal string.
|
|
34
|
-
DIFFICULTY is a 16-character hexadecimal string (default: FFFFFFF800000000)
|
|
35
|
-
|
|
36
|
-
Report bugs: <bug-nano-pow@zoso.dev>
|
|
37
|
-
Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
38
|
-
`
|