nano-pow 4.1.5 → 4.1.6

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/dist/bin/cli.js CHANGED
@@ -1,18 +1,78 @@
1
1
  #!/usr/bin/env node
2
- //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
3
- //! SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ // src/bin/cli.ts
4
4
  import { spawn } from "node:child_process";
5
5
  import { getRandomValues } from "node:crypto";
6
6
  import { createInterface } from "node:readline/promises";
7
+
8
+ // src/utils/index.ts
9
+ function average(times) {
10
+ if (times == null || times.length === 0) return {};
11
+ let count = times.length;
12
+ let min = Number.MAX_SAFE_INTEGER;
13
+ let logarithms, max, median, rate, reciprocals, total, truncated, truncatedCount;
14
+ logarithms = max = median = rate = reciprocals = total = truncated = truncatedCount = 0;
15
+ times.sort((a, b) => a - b);
16
+ for (let i = 0; i < count; i++) {
17
+ const time = times[i];
18
+ total += time;
19
+ reciprocals += 1 / time;
20
+ logarithms += Math.log(time);
21
+ min = Math.min(min, time);
22
+ max = Math.max(max, time);
23
+ if (i + 1 === Math.ceil(count / 2)) median = time;
24
+ if (count < 3 || i > 0.1 * count && i < 0.9 * (count - 1)) {
25
+ truncated += time;
26
+ truncatedCount++;
27
+ }
28
+ }
29
+ return {
30
+ count,
31
+ total,
32
+ min,
33
+ max,
34
+ median,
35
+ arithmetic: total / count,
36
+ harmonic: count / reciprocals,
37
+ geometric: Math.exp(logarithms / count),
38
+ truncated: truncated / truncatedCount,
39
+ rate: 1e3 * truncatedCount / (truncated || total)
40
+ };
41
+ }
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
+
67
+ // src/bin/cli.ts
68
+ //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
69
+ //! SPDX-License-Identifier: GPL-3.0-or-later
7
70
  process.title = "NanoPow CLI";
8
71
  delete process.env.NANO_POW_DEBUG;
9
72
  delete process.env.NANO_POW_EFFORT;
10
73
  delete process.env.NANO_POW_PORT;
11
- function log(...args2) {
12
- if (process.env.NANO_POW_DEBUG) console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args2);
13
- }
14
- const hashes = [];
15
- const stdinErrors = [];
74
+ var hashes = [];
75
+ var stdinErrors = [];
16
76
  if (!process.stdin.isTTY) {
17
77
  const stdin = createInterface({
18
78
  input: process.stdin
@@ -20,14 +80,14 @@ if (!process.stdin.isTTY) {
20
80
  let i = 0;
21
81
  for await (const line of stdin) {
22
82
  i++;
23
- if (/^[0-9A-Fa-f]{64}$/.test(line)) {
83
+ if (isHex(line, 64)) {
24
84
  hashes.push(line);
25
85
  } else {
26
86
  stdinErrors.push(`Skipping invalid stdin input line ${i}`);
27
87
  }
28
88
  }
29
89
  }
30
- const args = process.argv.slice(2);
90
+ var args = process.argv.slice(2);
31
91
  if (hashes.length === 0 && args.length === 0 || args.some((v) => v === "--help" || v === "-h")) {
32
92
  console.log(
33
93
  `Usage: nano-pow [OPTION]... BLOCKHASH...
@@ -47,7 +107,7 @@ If using --validate, results will also include validity properties.
47
107
 
48
108
  If validating a nonce, it must be a 16-character hexadecimal value.
49
109
  Effort must be a decimal number between 1-32.
50
- Difficulty must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.
110
+ Difficulty must be a hexadecimal string between 0-FFFFFFFFFFFFFFFF.
51
111
 
52
112
  Report bugs: <bug-nano-pow@zoso.dev>
53
113
  Full documentation: <https://www.npmjs.com/package/nano-pow>
@@ -55,48 +115,53 @@ Full documentation: <https://www.npmjs.com/package/nano-pow>
55
115
  );
56
116
  process.exit(0);
57
117
  }
58
- const inArgs = [];
59
- while (/^[0-9A-Fa-f]{64}$/.test(args[args.length - 1] ?? "")) {
118
+ var inArgs = [];
119
+ while (isHex(args[args.length - 1], 64)) {
60
120
  inArgs.unshift(args.pop());
61
121
  }
62
122
  hashes.push(...inArgs);
63
- let isBatch = false;
64
- let isBenchmark = false;
65
- const body = {
123
+ var isBatch = false;
124
+ var isBenchmark = false;
125
+ var body = {
66
126
  action: "work_generate"
67
127
  };
68
128
  for (let i = 0; i < args.length; i++) {
69
129
  switch (args[i]) {
70
130
  case "--validate":
71
131
  case "-v": {
72
- if (args[i + 1] == null) throw new Error("Missing argument for work validation");
73
- if (!/^[0-9A-Fa-f]{16}$/.test(args[i + 1])) throw new Error("Invalid work to validate");
132
+ const v = args[i + 1];
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");
74
136
  body.action = "work_validate";
75
- body.work = args[i + 1];
137
+ body.work = v;
76
138
  break;
77
139
  }
78
140
  case "--difficulty":
79
141
  case "-d": {
80
- if (args[i + 1] == null) throw new Error("Missing argument for difficulty");
81
- if (!/^[0-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(args[i + 1])) throw new Error("Invalid difficulty");
82
- body.difficulty = args[i + 1];
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;
83
146
  break;
84
147
  }
85
148
  case "--effort":
86
149
  case "-e": {
87
- if (args[i + 1] == null) throw new Error("Missing argument for effort");
88
- if (!/^[0-9]{0,2}$/.test(args[i + 1])) throw new Error("Invalid effort");
89
- process.env.NANO_POW_EFFORT = args[i + 1];
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;
90
154
  break;
91
155
  }
92
156
  case "--benchmark": {
93
- if (args[i + 1] == null) throw new Error("Missing argument for benchmark");
94
- if (!(+args[i + 1] > 0)) throw new Error("Invalid benchmark count");
157
+ const b = args[i + 1];
158
+ if (b == null) throw new Error("Missing argument for benchmark");
159
+ const count = +b;
160
+ if (count < 1) throw new Error("Invalid benchmark count");
95
161
  const random = new Uint8Array(32);
96
- while (hashes.length < +args[i + 1]) {
162
+ while (hashes.length < count) {
97
163
  getRandomValues(random);
98
- const byteArray = [...random].map((byte) => byte.toString(16).padStart(2, "0"));
99
- hashes.push(byteArray.join(""));
164
+ hashes.push(Buffer.from(random).toString("hex"));
100
165
  }
101
166
  isBenchmark = true;
102
167
  break;
@@ -121,53 +186,66 @@ for (const stdinErr of stdinErrors) {
121
186
  log(stdinErr);
122
187
  }
123
188
  log("Starting NanoPow CLI");
124
- const server = spawn(process.execPath, [new URL(import.meta.resolve("./server.js")).pathname], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
125
- const port = await new Promise((resolve, reject) => {
126
- server.on("message", (msg) => {
127
- if (msg.type === "console") {
128
- log(msg.text);
129
- }
130
- if (msg.type === "listening") {
131
- if (msg.port != null) {
132
- log(`Server listening on port ${msg.port}`);
133
- resolve(msg.port);
134
- } else {
135
- reject("Server failed to provide port");
136
- }
137
- }
138
- });
189
+ var server = spawn(
190
+ process.execPath,
191
+ [new URL(import.meta.resolve("./server.js")).pathname],
192
+ { stdio: ["pipe", "pipe", "pipe", "ipc"] }
193
+ );
194
+ server.once("error", (err) => {
195
+ log(err);
196
+ process.exit(1);
139
197
  });
140
- const results = [];
141
- const aborter = new AbortController();
142
- if (isBenchmark) console.log("Starting benchmark...");
143
- const start = performance.now();
144
- for (const hash of hashes) {
145
- try {
146
- body.hash = hash;
147
- const kill = setTimeout(() => aborter.abort(), 6e4);
148
- const response = await fetch(`http://localhost:${port}`, {
149
- method: "POST",
150
- body: JSON.stringify(body),
151
- signal: aborter.signal
152
- });
153
- clearTimeout(kill);
154
- const result = await response.json();
155
- if (isBatch || isBenchmark) {
156
- results.push(result);
198
+ server.on("message", async (msg) => {
199
+ if (msg.type === "console") {
200
+ log(msg.message);
201
+ }
202
+ if (msg.type === "listening") {
203
+ const port = +msg.message;
204
+ if (port > -1) {
205
+ log(`CLI server listening on port ${port}`);
206
+ try {
207
+ await execute(port);
208
+ } catch {
209
+ log(`Error executing ${body.action}`);
210
+ }
157
211
  } else {
158
- console.log(result);
212
+ log("Server failed to provide port");
159
213
  }
160
- } catch (err) {
161
- log(err);
162
214
  }
163
- }
164
- const end = performance.now();
165
- if (isBatch && !isBenchmark) console.log(results);
166
- if (process.env.NANO_POW_DEBUG || isBenchmark) {
167
- console.log(end - start, "ms total |", (end - start) / hashes.length, "ms avg");
168
- }
215
+ });
169
216
  server.on("close", (code) => {
170
217
  log(`Server closed with exit code ${code}`);
171
218
  process.exit(code);
172
219
  });
173
- server.kill();
220
+ async function execute(port) {
221
+ const results = [];
222
+ if (isBenchmark) console.log("Running benchmark...");
223
+ let start = 0;
224
+ const times = [];
225
+ for (const hash of hashes) {
226
+ try {
227
+ const aborter = new AbortController();
228
+ const kill = setTimeout(() => aborter.abort(), 6e4);
229
+ body.hash = hash;
230
+ start = performance.now();
231
+ const response = await fetch(`http://localhost:${port}`, {
232
+ method: "POST",
233
+ body: JSON.stringify(body),
234
+ signal: aborter.signal
235
+ });
236
+ clearTimeout(kill);
237
+ const result = await response.json();
238
+ if (isBatch || isBenchmark) {
239
+ results.push(result);
240
+ times.push(performance.now() - start);
241
+ } else {
242
+ console.log(result);
243
+ }
244
+ } catch (err) {
245
+ log(err);
246
+ }
247
+ }
248
+ if (isBatch && !isBenchmark) console.log(results);
249
+ if (process.env.NANO_POW_DEBUG || isBenchmark) console.log(average(times));
250
+ server.kill();
251
+ }
@@ -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
- node "$SCRIPT_DIR"/server.js --max-http-header-size=1024 >> "$NANO_POW_LOGS"/nano-pow-server-$(date -I).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid;
13
+ node "$SCRIPT_DIR"/server.js --max-http-header-size=1024 --max-old-space-size=256 >> "$NANO_POW_LOGS"/nano-pow-server-$(date -I).log 2>&1 & echo "$!" > "$NANO_POW_HOME"/server.pid;
14
14
  sleep 0.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 "$SCRIPT_DIR"/cli.js "$@";
19
+ node "$SCRIPT_DIR"/cli.js --max-old-space-size=256 "$@";
20
20
  fi;