nano-pow 4.1.7 → 5.0.1
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 +28 -14
- package/dist/bin/cli.js +347 -112
- package/dist/bin/nano-pow.sh +3 -3
- package/dist/bin/server.js +291 -80
- package/dist/main.min.js +1450 -732
- package/dist/types.d.ts +51 -131
- package/docs/benchmarks.md +0 -1
- package/docs/nano-pow.1 +24 -6
- package/docs/nano-pow.service +22 -0
- package/package.json +18 -10
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ _Proof-of-work generation and validation with WebGPU/WebGL for Nano cryptocurren
|
|
|
8
8
|
|
|
9
9
|
NanoPow uses WebGPU to generate proof-of-work nonces meeting the requirements
|
|
10
10
|
of the Nano cryptocurrency. WebGPU is cutting edge technology, so for browsers
|
|
11
|
-
which do not yet support it,
|
|
11
|
+
which do not yet support it, WebGL 2.0 and WASM implementations are available
|
|
12
|
+
as fallbacks.
|
|
12
13
|
|
|
13
14
|
All calculations take place client-side, so nonces can be generated offline and
|
|
14
15
|
cached for the next transaction block. For more information about the
|
|
@@ -36,12 +37,6 @@ import { NanoPow } from 'nano-pow'
|
|
|
36
37
|
import NanoPow from 'nano-pow'
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
A specific API can be explicitly imported:
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
import { NanoPowGpu, NanoPowGl } from 'nano-pow'
|
|
43
|
-
```
|
|
44
|
-
|
|
45
40
|
Use it directly on a webpage with a script module:
|
|
46
41
|
|
|
47
42
|
```html
|
|
@@ -75,16 +70,34 @@ const { valid } = await NanoPow.work_validate(work, hash)
|
|
|
75
70
|
### Options
|
|
76
71
|
```javascript
|
|
77
72
|
const options = {
|
|
78
|
-
// default
|
|
73
|
+
// default best available in order: webgpu => webgl => wasm => cpu
|
|
74
|
+
api: 'webgpu',
|
|
75
|
+
// default FFFFFFF800000000 for send/change blocks
|
|
79
76
|
difficulty: 'FFFFFFC000000000',
|
|
80
|
-
// default
|
|
81
|
-
effort:
|
|
77
|
+
// default 4, valid range 1-32
|
|
78
|
+
effort: 2,
|
|
82
79
|
// default false
|
|
83
80
|
debug: true
|
|
84
81
|
}
|
|
85
82
|
const { work } = await NanoPow.work_generate(hash, options)
|
|
86
83
|
```
|
|
87
84
|
|
|
85
|
+
#### What is "effort"?
|
|
86
|
+
NanoPow's "effort" metric is an abstraction of various GPU and CPU capabilities.
|
|
87
|
+
Different systems will have different optimal settings, but as a general rule of
|
|
88
|
+
thumb:
|
|
89
|
+
|
|
90
|
+
* WebGPU must strike a balance between the overhead of dispatching work to the
|
|
91
|
+
GPU and the time it takes to compute the dispatch itself. Start with a low value
|
|
92
|
+
like 2 or 4.
|
|
93
|
+
* WegGL works by drawing to an invisible 2-D canvas that is `effort * 256`
|
|
94
|
+
pixels long on each side. Since PoW speed in this case depends on resolution
|
|
95
|
+
_and_ framerate, push for a value as high as the GPU can support. For example, a
|
|
96
|
+
GPU that can draw 4096 x 4096 at 15 FPS should be set around 16 effort.
|
|
97
|
+
* WASM does not use the GPU at all and instead depends on Web Workers for CPU
|
|
98
|
+
multi-threading capabilities. Set effort equal to the number of physical cores
|
|
99
|
+
in the CPU.
|
|
100
|
+
|
|
88
101
|
## Executables
|
|
89
102
|
NanoPow can be installed globally and executed from the command line. This is
|
|
90
103
|
useful for systems without a graphical interface.
|
|
@@ -137,11 +150,11 @@ detached process, and it can also be started manually to customize behavior by
|
|
|
137
150
|
executing the server script directly.
|
|
138
151
|
|
|
139
152
|
#### Environment Variables
|
|
140
|
-
`
|
|
153
|
+
`NANO_POW_DEBUG`: enable additional logging saved to the HOME directory
|
|
141
154
|
|
|
142
|
-
`NANO_POW_EFFORT
|
|
155
|
+
`NANO_POW_EFFORT`: increase or decrease demand on the GPU
|
|
143
156
|
|
|
144
|
-
`
|
|
157
|
+
`NANO_POW_PORT`: override the default port 5040
|
|
145
158
|
|
|
146
159
|
```console
|
|
147
160
|
$ # Launch the server and detach from the current session
|
|
@@ -196,7 +209,8 @@ every last bit of speed and performance out of it.
|
|
|
196
209
|
A few basic tests are availabe in the source repository.
|
|
197
210
|
* `test/index.html` in the source repository contains a web interface to change
|
|
198
211
|
execution options and compare results.
|
|
199
|
-
* `test/script.sh`
|
|
212
|
+
* `test/script.sh` runs some basic benchmarks to check the CLI, and then it
|
|
213
|
+
starts the `nano-pow` server and sends some validate and generate requests.
|
|
200
214
|
|
|
201
215
|
## Building
|
|
202
216
|
1. Clone source
|
package/dist/bin/cli.js
CHANGED
|
@@ -5,69 +5,238 @@ import { spawn } from "node:child_process";
|
|
|
5
5
|
import { getRandomValues } from "node:crypto";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
|
|
8
|
+
// src/utils/api-support/wasm.ts
|
|
9
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
10
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
11
|
+
var wasm = { isSupported: false };
|
|
12
|
+
Object.defineProperty(wasm, "isSupported", {
|
|
13
|
+
get: async function() {
|
|
14
|
+
let isWasmSupported = false;
|
|
15
|
+
try {
|
|
16
|
+
const wasmBuffer = new Uint8Array([
|
|
17
|
+
0,
|
|
18
|
+
97,
|
|
19
|
+
115,
|
|
20
|
+
109,
|
|
21
|
+
1,
|
|
22
|
+
0,
|
|
23
|
+
0,
|
|
24
|
+
0,
|
|
25
|
+
// WASM magic header and version
|
|
26
|
+
1,
|
|
27
|
+
4,
|
|
28
|
+
// Type section, 4 byte descriptor
|
|
29
|
+
1,
|
|
30
|
+
96,
|
|
31
|
+
0,
|
|
32
|
+
0,
|
|
33
|
+
// 1 type, type function, 0 params, 0 returns
|
|
34
|
+
3,
|
|
35
|
+
2,
|
|
36
|
+
// Function section, 2 byte descriptor
|
|
37
|
+
1,
|
|
38
|
+
0,
|
|
39
|
+
// 1 function, ID 0
|
|
40
|
+
10,
|
|
41
|
+
23,
|
|
42
|
+
// Code section, 23 bytes
|
|
43
|
+
1,
|
|
44
|
+
21,
|
|
45
|
+
// 1 function body, 21 bytes
|
|
46
|
+
0,
|
|
47
|
+
// 0 local variables
|
|
48
|
+
253,
|
|
49
|
+
12,
|
|
50
|
+
// v128.const
|
|
51
|
+
0,
|
|
52
|
+
0,
|
|
53
|
+
0,
|
|
54
|
+
0,
|
|
55
|
+
0,
|
|
56
|
+
0,
|
|
57
|
+
0,
|
|
58
|
+
0,
|
|
59
|
+
0,
|
|
60
|
+
0,
|
|
61
|
+
0,
|
|
62
|
+
0,
|
|
63
|
+
0,
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
// assign i64x2 values
|
|
68
|
+
26,
|
|
69
|
+
11
|
|
70
|
+
// Drop result, end function
|
|
71
|
+
]);
|
|
72
|
+
const module = await WebAssembly.compile(wasmBuffer);
|
|
73
|
+
const instance = await WebAssembly.instantiate(module);
|
|
74
|
+
isWasmSupported = instance instanceof WebAssembly.Instance;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn("WASM is not supported in this environment.\n", err);
|
|
77
|
+
isWasmSupported = false;
|
|
78
|
+
} finally {
|
|
79
|
+
delete this.isSupported;
|
|
80
|
+
this.isSupported = isWasmSupported;
|
|
81
|
+
return this.isSupported;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// src/utils/api-support/webgl.ts
|
|
87
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
88
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
89
|
+
var webgl = { isSupported: false };
|
|
90
|
+
Object.defineProperty(webgl, "isSupported", {
|
|
91
|
+
get: async function() {
|
|
92
|
+
let isWebglSupported = false;
|
|
93
|
+
try {
|
|
94
|
+
const gl = new OffscreenCanvas(0, 0)?.getContext?.("webgl2");
|
|
95
|
+
isWebglSupported = gl instanceof WebGL2RenderingContext;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.warn("WebGL is not supported in this environment.\n", err);
|
|
98
|
+
isWebglSupported = false;
|
|
99
|
+
} finally {
|
|
100
|
+
delete this.isSupported;
|
|
101
|
+
this.isSupported = isWebglSupported;
|
|
102
|
+
return this.isSupported;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// src/utils/api-support/webgpu.ts
|
|
108
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
109
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
110
|
+
var webgpu = { isSupported: false };
|
|
111
|
+
Object.defineProperty(webgpu, "isSupported", {
|
|
112
|
+
get: async function() {
|
|
113
|
+
let isWebgpuSupported = false;
|
|
114
|
+
try {
|
|
115
|
+
const adapter = await navigator?.gpu?.requestAdapter?.();
|
|
116
|
+
isWebgpuSupported = adapter instanceof GPUAdapter;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.warn("WebGPU is not supported in this environment.\n", err);
|
|
119
|
+
isWebgpuSupported = false;
|
|
120
|
+
} finally {
|
|
121
|
+
delete this.isSupported;
|
|
122
|
+
this.isSupported = isWebgpuSupported;
|
|
123
|
+
return this.isSupported;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// src/utils/api-support/index.ts
|
|
129
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
130
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
131
|
+
|
|
132
|
+
// src/utils/bigint.ts
|
|
133
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
134
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
135
|
+
|
|
136
|
+
// src/utils/logger.ts
|
|
137
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
138
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
139
|
+
var Logger = class {
|
|
140
|
+
isEnabled = false;
|
|
141
|
+
groups = {};
|
|
142
|
+
groupStart(name) {
|
|
143
|
+
if (this.isEnabled) {
|
|
144
|
+
console.groupCollapsed(name);
|
|
145
|
+
this.groups[name] = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
groupEnd(name) {
|
|
149
|
+
if (this.groups[name]) {
|
|
150
|
+
console.groupEnd();
|
|
151
|
+
delete this.groups[name];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
log(...args2) {
|
|
155
|
+
if (this.isEnabled) {
|
|
156
|
+
const datetime = new Date(Date.now()).toLocaleString(Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US", { hour12: false, dateStyle: "medium", timeStyle: "medium" });
|
|
157
|
+
for (let i = 0; i < args2.length; i++) {
|
|
158
|
+
if (typeof args2[i] === "string") {
|
|
159
|
+
args2[i] = args2[i].replace(datetime, "").trimStart();
|
|
160
|
+
}
|
|
161
|
+
if (args2[i] instanceof Error) {
|
|
162
|
+
if ("stack" in args2[i]) {
|
|
163
|
+
args2.splice(i + 1, 0, args2[i].stack);
|
|
164
|
+
} else if ("message" in args2[i]) {
|
|
165
|
+
args2.splice(i + 1, 0, args2[i].message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const entry = `${datetime} ${globalThis.process?.title ?? "NanoPow"}[${globalThis.process?.pid ?? "browser"}]:`;
|
|
170
|
+
console.log(entry, ...args2);
|
|
171
|
+
globalThis.process?.send?.({ type: "console", data: `${entry} ${args2}` });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/utils/queue.ts
|
|
177
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
178
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
179
|
+
|
|
8
180
|
// src/utils/index.ts
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
181
|
+
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
182
|
+
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
183
|
+
function stats(times) {
|
|
184
|
+
if (times == null || times.length === 0) return null;
|
|
185
|
+
const count = times.length;
|
|
186
|
+
const truncatedStart = Math.floor(count * 0.1);
|
|
187
|
+
const truncatedEnd = count - truncatedStart;
|
|
188
|
+
const truncatedCount = truncatedEnd - truncatedStart;
|
|
12
189
|
let min = Number.MAX_SAFE_INTEGER;
|
|
13
|
-
let logarithms, max, median,
|
|
14
|
-
logarithms = max = median =
|
|
190
|
+
let logarithms, max, median, reciprocals, total;
|
|
191
|
+
logarithms = max = median = reciprocals = total = 0;
|
|
192
|
+
let truncatedMin = Number.MAX_SAFE_INTEGER;
|
|
193
|
+
let truncatedLogarithms, truncatedMax, truncatedReciprocals, truncatedTotal;
|
|
194
|
+
truncatedLogarithms = truncatedMax = truncatedReciprocals = truncatedTotal = 0;
|
|
15
195
|
times.sort((a, b) => a - b);
|
|
16
196
|
for (let i = 0; i < count; i++) {
|
|
17
197
|
const time = times[i];
|
|
18
198
|
total += time;
|
|
19
|
-
reciprocals += 1 / time;
|
|
20
199
|
logarithms += Math.log(time);
|
|
200
|
+
reciprocals += 1 / time;
|
|
21
201
|
min = Math.min(min, time);
|
|
22
202
|
max = Math.max(max, time);
|
|
23
|
-
if (i
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
203
|
+
if (i === Math.floor((count - 1) / 2)) median = time;
|
|
204
|
+
if (i === Math.floor(count / 2) && count % 2 === 0) median = (median + time) / 2;
|
|
205
|
+
}
|
|
206
|
+
for (let i = truncatedStart; i < truncatedEnd; i++) {
|
|
207
|
+
const time = times[i];
|
|
208
|
+
truncatedTotal += time;
|
|
209
|
+
truncatedLogarithms += Math.log(time);
|
|
210
|
+
truncatedReciprocals += 1 / time;
|
|
211
|
+
truncatedMin = Math.min(truncatedMin, time);
|
|
212
|
+
truncatedMax = Math.max(truncatedMax, time);
|
|
28
213
|
}
|
|
29
214
|
return {
|
|
30
215
|
count,
|
|
31
216
|
total,
|
|
217
|
+
rate: 1e3 * count / total,
|
|
32
218
|
min,
|
|
33
219
|
max,
|
|
34
220
|
median,
|
|
35
221
|
arithmetic: total / count,
|
|
36
|
-
harmonic: count / reciprocals,
|
|
37
222
|
geometric: Math.exp(logarithms / count),
|
|
38
|
-
|
|
39
|
-
|
|
223
|
+
harmonic: count / reciprocals,
|
|
224
|
+
truncatedCount,
|
|
225
|
+
truncatedTotal,
|
|
226
|
+
truncatedRate: 1e3 * truncatedCount / truncatedTotal,
|
|
227
|
+
truncatedMin,
|
|
228
|
+
truncatedMax,
|
|
229
|
+
truncatedArithmetic: truncatedTotal / truncatedCount,
|
|
230
|
+
truncatedGeometric: Math.exp(truncatedLogarithms / truncatedCount),
|
|
231
|
+
truncatedHarmonic: truncatedCount / truncatedReciprocals
|
|
40
232
|
};
|
|
41
233
|
}
|
|
42
|
-
function isHex(input, min, max) {
|
|
43
|
-
if (typeof input !== "string") {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if (typeof min !== "undefined" && typeof min !== "number") {
|
|
47
|
-
throw new Error(`Invalid argument for parameter 'min'`);
|
|
48
|
-
}
|
|
49
|
-
if (typeof max !== "undefined" && typeof max !== "number") {
|
|
50
|
-
throw new Error(`Invalid argument for parameter 'max'`);
|
|
51
|
-
}
|
|
52
|
-
const range = min === void 0 && max === void 0 ? "+" : `{${min ?? "0"},${max ?? ""}}`;
|
|
53
|
-
const regexp = new RegExp(`^[0-9A-Fa-f]${range}$`, "m");
|
|
54
|
-
return regexp.test(input);
|
|
55
|
-
}
|
|
56
|
-
function isNotHex(input, min, max) {
|
|
57
|
-
return !isHex(input, min, max);
|
|
58
|
-
}
|
|
59
|
-
function log(...args2) {
|
|
60
|
-
if (process?.env?.NANO_POW_DEBUG) {
|
|
61
|
-
const entry = `${new Date(Date.now()).toLocaleString(Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US", { hour12: false, dateStyle: "medium", timeStyle: "medium" })} NanoPow[${process.pid}]: ${args2}`;
|
|
62
|
-
console.log(entry);
|
|
63
|
-
process.send?.({ type: "console", message: entry });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
234
|
|
|
67
235
|
// src/bin/cli.ts
|
|
68
236
|
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
|
|
69
237
|
//! SPDX-License-Identifier: GPL-3.0-or-later
|
|
70
238
|
process.title = "NanoPow CLI";
|
|
239
|
+
var logger = new Logger();
|
|
71
240
|
delete process.env.NANO_POW_DEBUG;
|
|
72
241
|
delete process.env.NANO_POW_EFFORT;
|
|
73
242
|
delete process.env.NANO_POW_PORT;
|
|
@@ -80,7 +249,7 @@ if (!process.stdin.isTTY) {
|
|
|
80
249
|
let i = 0;
|
|
81
250
|
for await (const line of stdin) {
|
|
82
251
|
i++;
|
|
83
|
-
if (
|
|
252
|
+
if (/^[A-Fa-f\d]{64}$/.test(line)) {
|
|
84
253
|
hashes.push(line);
|
|
85
254
|
} else {
|
|
86
255
|
stdinErrors.push(`Skipping invalid stdin input line ${i}`);
|
|
@@ -89,13 +258,13 @@ if (!process.stdin.isTTY) {
|
|
|
89
258
|
}
|
|
90
259
|
var args = process.argv.slice(2);
|
|
91
260
|
if (hashes.length === 0 && args.length === 0 || args.some((v) => v === "--help" || v === "-h")) {
|
|
92
|
-
console.log(
|
|
93
|
-
`Usage: nano-pow [OPTION]... BLOCKHASH...
|
|
261
|
+
console.log(`Usage: nano-pow [OPTION]... BLOCKHASH...
|
|
94
262
|
Generate work for BLOCKHASH, or multiple work values for BLOCKHASH(es)
|
|
95
263
|
BLOCKHASH is a 64-character hexadecimal string. Multiple blockhashes must be separated by whitespace or line breaks.
|
|
96
264
|
Prints the result as a Javascript object to standard output as soon as it is calculated.
|
|
97
265
|
If using --batch, results are printed only after all BLOCKHASH(es) have be processed.
|
|
98
266
|
If using --validate, results will also include validity properties.
|
|
267
|
+
|
|
99
268
|
-b, --batch process all data before returning final results as array
|
|
100
269
|
-d, --difficulty <value> override the minimum difficulty value
|
|
101
270
|
-e, --effort <value> increase demand on GPU processing
|
|
@@ -104,6 +273,7 @@ If using --validate, results will also include validity properties.
|
|
|
104
273
|
-h, --help show this dialog
|
|
105
274
|
--debug enable additional logging output
|
|
106
275
|
--benchmark <value> generate work for specified number of random hashes
|
|
276
|
+
--score <value> used with --benchmark to run specified number of times and calculate work-per-second
|
|
107
277
|
|
|
108
278
|
If validating a nonce, it must be a 16-character hexadecimal value.
|
|
109
279
|
Effort must be a decimal number between 1-32.
|
|
@@ -111,46 +281,31 @@ Difficulty must be a hexadecimal string between 0-FFFFFFFFFFFFFFFF.
|
|
|
111
281
|
|
|
112
282
|
Report bugs: <bug-nano-pow@zoso.dev>
|
|
113
283
|
Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
114
|
-
`
|
|
115
|
-
);
|
|
284
|
+
`);
|
|
116
285
|
process.exit(0);
|
|
117
286
|
}
|
|
118
287
|
var inArgs = [];
|
|
119
|
-
|
|
120
|
-
|
|
288
|
+
var isParsingHash = true;
|
|
289
|
+
while (isParsingHash) {
|
|
290
|
+
if (!/^[A-Fa-f\d]{16}$/.test(args[args.length - 1])) break;
|
|
291
|
+
try {
|
|
292
|
+
inArgs.unshift(args.pop());
|
|
293
|
+
} catch {
|
|
294
|
+
isParsingHash = false;
|
|
295
|
+
}
|
|
121
296
|
}
|
|
122
297
|
hashes.push(...inArgs);
|
|
123
298
|
var isBatch = false;
|
|
124
299
|
var isBenchmark = false;
|
|
300
|
+
var runs = 1;
|
|
125
301
|
var body = {
|
|
126
302
|
action: "work_generate"
|
|
127
303
|
};
|
|
128
304
|
for (let i = 0; i < args.length; i++) {
|
|
129
305
|
switch (args[i]) {
|
|
130
|
-
case "--
|
|
131
|
-
case "-
|
|
132
|
-
|
|
133
|
-
if (v == null) throw new Error("Missing argument for work validation");
|
|
134
|
-
if (isNotHex(v, 16)) throw new Error("Invalid work to validate");
|
|
135
|
-
if (hashes.length !== 1) throw new Error("Validate accepts exactly one hash");
|
|
136
|
-
body.action = "work_validate";
|
|
137
|
-
body.work = v;
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
case "--difficulty":
|
|
141
|
-
case "-d": {
|
|
142
|
-
const d = args[i + 1];
|
|
143
|
-
if (d == null) throw new Error("Missing argument for difficulty");
|
|
144
|
-
if (isNotHex(d, 1, 16)) throw new Error("Invalid difficulty");
|
|
145
|
-
body.difficulty = d;
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
case "--effort":
|
|
149
|
-
case "-e": {
|
|
150
|
-
const e = args[i + 1];
|
|
151
|
-
if (e == null) throw new Error("Missing argument for effort");
|
|
152
|
-
if (parseInt(e) < 1 || parseInt(e) > 32) throw new Error("Invalid effort");
|
|
153
|
-
process.env.NANO_POW_EFFORT = e;
|
|
306
|
+
case "--batch":
|
|
307
|
+
case "-b": {
|
|
308
|
+
isBatch = true;
|
|
154
309
|
break;
|
|
155
310
|
}
|
|
156
311
|
case "--benchmark": {
|
|
@@ -167,12 +322,42 @@ for (let i = 0; i < args.length; i++) {
|
|
|
167
322
|
break;
|
|
168
323
|
}
|
|
169
324
|
case "--debug": {
|
|
325
|
+
logger.isEnabled = true;
|
|
170
326
|
process.env.NANO_POW_DEBUG = "true";
|
|
171
327
|
break;
|
|
172
328
|
}
|
|
173
|
-
case "--
|
|
174
|
-
case "-
|
|
175
|
-
|
|
329
|
+
case "--difficulty":
|
|
330
|
+
case "-d": {
|
|
331
|
+
const d = args[i + 1];
|
|
332
|
+
if (d == null) throw new Error("Missing argument for difficulty");
|
|
333
|
+
if (!/^[A-Fa-f\d]{16}$/.test(d)) throw new Error("Invalid argument for difficulty");
|
|
334
|
+
body.difficulty = d;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case "--effort":
|
|
338
|
+
case "-e": {
|
|
339
|
+
const e = args[i + 1];
|
|
340
|
+
if (e == null) throw new Error("Missing argument for effort");
|
|
341
|
+
if (parseInt(e) < 1 || parseInt(e) > 32) throw new Error("Invalid effort");
|
|
342
|
+
process.env.NANO_POW_EFFORT = e;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case "--score": {
|
|
346
|
+
const s = args[i + 1];
|
|
347
|
+
if (s == null) throw new Error("Missing argument for score");
|
|
348
|
+
const count = +s;
|
|
349
|
+
if (count < 1) throw new Error("Invalid score runs count");
|
|
350
|
+
runs = count;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case "--validate":
|
|
354
|
+
case "-v": {
|
|
355
|
+
const v = args[i + 1];
|
|
356
|
+
if (v == null) throw new Error("Missing argument for work validation");
|
|
357
|
+
if (!/^[A-Fa-f\d]{16}$/.test(v)) throw new Error("Invalid argument for work validation");
|
|
358
|
+
if (hashes.length !== 1) throw new Error("Validate accepts exactly one hash");
|
|
359
|
+
body.action = "work_validate";
|
|
360
|
+
body.work = v;
|
|
176
361
|
break;
|
|
177
362
|
}
|
|
178
363
|
}
|
|
@@ -181,71 +366,121 @@ if (hashes.length === 0) {
|
|
|
181
366
|
console.error("Invalid block hash input");
|
|
182
367
|
process.exit(1);
|
|
183
368
|
}
|
|
184
|
-
log("CLI args:", ...args);
|
|
369
|
+
logger.log("CLI args: ", ...args);
|
|
185
370
|
for (const stdinErr of stdinErrors) {
|
|
186
|
-
log(stdinErr);
|
|
371
|
+
logger.log(stdinErr);
|
|
187
372
|
}
|
|
188
|
-
log("
|
|
373
|
+
logger.log("launching server");
|
|
189
374
|
var server = spawn(
|
|
190
375
|
process.execPath,
|
|
191
376
|
[new URL(import.meta.resolve("./server.js")).pathname],
|
|
192
377
|
{ stdio: ["pipe", "pipe", "pipe", "ipc"] }
|
|
193
378
|
);
|
|
194
379
|
server.once("error", (err) => {
|
|
195
|
-
log(err);
|
|
380
|
+
logger.log(err);
|
|
196
381
|
process.exit(1);
|
|
197
382
|
});
|
|
198
383
|
server.on("message", async (msg) => {
|
|
199
|
-
if (msg.type === "
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
384
|
+
if (typeof msg === "object" && msg != null && "type" in msg && typeof msg.type === "string" && "data" in msg && typeof msg.data === "string") {
|
|
385
|
+
if (msg.type === "console") {
|
|
386
|
+
logger.log(msg.data);
|
|
387
|
+
}
|
|
388
|
+
if (msg.type === "listening") {
|
|
389
|
+
if (msg.data === "ipc") {
|
|
390
|
+
logger.log(`CLI connected to server over IPC`);
|
|
391
|
+
try {
|
|
392
|
+
if (isBenchmark) {
|
|
393
|
+
if (runs > 1) {
|
|
394
|
+
await score();
|
|
395
|
+
} else {
|
|
396
|
+
await benchmark();
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
await execute();
|
|
400
|
+
}
|
|
401
|
+
} catch {
|
|
402
|
+
logger.log(`Error executing ${body.action}`);
|
|
403
|
+
} finally {
|
|
404
|
+
server.kill();
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
logger.log("CLI server failed to connect over IPC");
|
|
210
408
|
}
|
|
211
|
-
} else {
|
|
212
|
-
log("Server failed to provide port");
|
|
213
409
|
}
|
|
410
|
+
} else {
|
|
411
|
+
logger.log("received invalid message from server", msg);
|
|
214
412
|
}
|
|
215
413
|
});
|
|
216
414
|
server.on("close", (code) => {
|
|
217
|
-
log(`Server closed with exit code ${code}`);
|
|
415
|
+
logger.log(`Server closed with exit code ${code}`);
|
|
218
416
|
process.exit(code);
|
|
219
417
|
});
|
|
220
|
-
async function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
let start = 0;
|
|
418
|
+
async function benchmark() {
|
|
419
|
+
console.log("Running benchmark...");
|
|
420
|
+
let start = 0, end = 0;
|
|
224
421
|
const times = [];
|
|
225
422
|
for (const hash of hashes) {
|
|
423
|
+
const kill = setTimeout(() => {
|
|
424
|
+
throw new Error("cli execution timed out");
|
|
425
|
+
}, 6e4);
|
|
426
|
+
body.hash = hash;
|
|
226
427
|
try {
|
|
227
|
-
const aborter = new AbortController();
|
|
228
|
-
const kill = setTimeout(() => aborter.abort(), 6e4);
|
|
229
|
-
body.hash = hash;
|
|
230
428
|
start = performance.now();
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
429
|
+
await request(body);
|
|
430
|
+
end = performance.now();
|
|
431
|
+
times.push(end - start);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
logger.log(err);
|
|
434
|
+
} finally {
|
|
236
435
|
clearTimeout(kill);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
console.log(stats(times));
|
|
439
|
+
return stats(times);
|
|
440
|
+
}
|
|
441
|
+
async function execute() {
|
|
442
|
+
const results = [];
|
|
443
|
+
for (const hash of hashes) {
|
|
444
|
+
const kill = setTimeout(() => {
|
|
445
|
+
throw new Error("cli execution timed out");
|
|
446
|
+
}, 6e4);
|
|
447
|
+
body.hash = hash;
|
|
448
|
+
try {
|
|
449
|
+
const result = await request(body);
|
|
450
|
+
isBatch ? results.push(result) : console.log(result);
|
|
451
|
+
} catch (err) {
|
|
452
|
+
logger.log(err);
|
|
453
|
+
} finally {
|
|
454
|
+
clearTimeout(kill);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (isBatch) console.log(results);
|
|
458
|
+
}
|
|
459
|
+
async function request(body2) {
|
|
460
|
+
return new Promise((resolve, reject) => {
|
|
461
|
+
const listener = async (msg) => {
|
|
462
|
+
if (typeof msg === "object" && msg != null && "type" in msg && typeof msg.type === "string" && "data" in msg && typeof msg.data === "string") {
|
|
463
|
+
if (msg.type === "ipc") {
|
|
464
|
+
resolve(JSON.parse(msg.data));
|
|
465
|
+
server.off("message", listener);
|
|
466
|
+
}
|
|
243
467
|
}
|
|
468
|
+
};
|
|
469
|
+
server.on("message", listener);
|
|
470
|
+
server.send({ type: "ipc", data: JSON.stringify(body2) }, (cb) => cb ? reject(cb) : logger.log("cli ipc request to server"));
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
async function score() {
|
|
474
|
+
console.log("Calculating work per second...");
|
|
475
|
+
const rates = [];
|
|
476
|
+
for (let i = 0; i < runs; i++) {
|
|
477
|
+
try {
|
|
478
|
+
const result = await benchmark();
|
|
479
|
+
logger.log(result);
|
|
480
|
+
if (result != null) rates.push(result.truncatedRate);
|
|
244
481
|
} catch (err) {
|
|
245
|
-
log(err);
|
|
482
|
+
logger.log(err);
|
|
246
483
|
}
|
|
247
484
|
}
|
|
248
|
-
|
|
249
|
-
if (process.env.NANO_POW_DEBUG || isBenchmark) console.log(average(times));
|
|
250
|
-
server.kill();
|
|
485
|
+
console.log(stats(rates)?.truncatedHarmonic, "wps");
|
|
251
486
|
}
|
package/dist/bin/nano-pow.sh
CHANGED
|
@@ -10,11 +10,11 @@ NANO_POW_LOGS="$NANO_POW_HOME"/logs;
|
|
|
10
10
|
mkdir -p "$NANO_POW_LOGS";
|
|
11
11
|
if [ "$1" = '--server' ]; then
|
|
12
12
|
shift;
|
|
13
|
-
|
|
14
|
-
sleep
|
|
13
|
+
npm start >> "$NANO_POW_LOGS"/nano-pow-server-$(date -I).log 2>&1 &
|
|
14
|
+
sleep 1;
|
|
15
15
|
if [ "$(ps | grep $(cat $NANO_POW_HOME/server.pid))" = '' ]; then
|
|
16
16
|
cat $(ls -td "$NANO_POW_LOGS"/* | head -n1);
|
|
17
17
|
fi;
|
|
18
18
|
else
|
|
19
|
-
node
|
|
19
|
+
node --max-old-space-size=256 "$SCRIPT_DIR"/cli.js "$@";
|
|
20
20
|
fi;
|