nano-pow 4.0.7 → 4.0.9
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 +87 -112
- package/dist/bin/server.js +150 -160
- package/dist/main.min.js +2 -6
- package/dist/types.d.ts +11 -3
- package/package.json +1 -1
- package/docs/index.js +0 -38
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/server.js
CHANGED
|
@@ -1,176 +1,166 @@
|
|
|
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 { launch } from "puppeteer";
|
|
5
|
+
import { subtle } from "node:crypto";
|
|
6
|
+
import { lookup } from "node:dns/promises";
|
|
7
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
7
8
|
import * as http from "node:http";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
const
|
|
9
|
+
import { hostname } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
process.title = "NanoPow Server";
|
|
12
|
+
const MAX_REQUEST_SIZE = 1024;
|
|
13
|
+
const MAX_BODY_SIZE = 158;
|
|
14
|
+
const DEBUG = !!(process.env.NANO_POW_DEBUG || false);
|
|
12
15
|
const EFFORT = +(process.env.NANO_POW_EFFORT || 8);
|
|
13
|
-
|
|
14
|
-
console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args);
|
|
15
|
-
}
|
|
16
|
-
log("Starting server");
|
|
17
|
-
const NanoPow = await fs.readFile(new URL("../main.min.js", import.meta.url), "utf-8");
|
|
16
|
+
const PORT = +(process.env.NANO_POW_PORT || 3e3);
|
|
18
17
|
let browser;
|
|
19
18
|
let page;
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
28
|
-
res.end(JSON.stringify({ error: "Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF." }));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
19
|
+
function log(...args) {
|
|
20
|
+
if (DEBUG) console.log(new Date(Date.now()).toLocaleString(), "NanoPow", args);
|
|
21
|
+
}
|
|
22
|
+
async function respond(res, data) {
|
|
23
|
+
let statusCode = 500;
|
|
24
|
+
let headers = { "Content-Type": "application/json" };
|
|
25
|
+
let response = "work_validate failed";
|
|
31
26
|
try {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const datastring = Buffer.concat(data).toString().replace(/\s+/g, "");
|
|
28
|
+
if (Buffer.byteLength(datastring) > MAX_BODY_SIZE) {
|
|
29
|
+
throw new Error("Invalid data.");
|
|
30
|
+
}
|
|
31
|
+
const { action, hash: hash2, work, difficulty } = JSON.parse(datastring);
|
|
32
|
+
if (action !== "work_generate" && action !== "work_validate") {
|
|
33
|
+
throw new Error("Invalid action. Must be work_generate or work_validate.");
|
|
34
|
+
}
|
|
35
|
+
if (!/^[0-9A-Fa-f]{64}$/.test(hash2 ?? "")) {
|
|
36
|
+
throw new Error("Invalid hash. Must be a 64-character hex string.");
|
|
37
|
+
}
|
|
38
|
+
if (difficulty && !/^[1-9A-Fa-f][0-9A-Fa-f]{0,15}$/.test(difficulty)) {
|
|
39
|
+
throw new Error("Invalid difficulty. Must be a hexadecimal string between 1-FFFFFFFFFFFFFFFF.");
|
|
40
|
+
}
|
|
41
|
+
if (action === "work_validate" && !/^[0-9A-Fa-f]{16}$/.test(work ?? "")) {
|
|
42
|
+
throw new Error("Invalid work. Must be a 16-character hex string.");
|
|
43
|
+
}
|
|
44
|
+
const options = {
|
|
45
|
+
debug: DEBUG,
|
|
46
|
+
effort: EFFORT,
|
|
47
|
+
threshold: difficulty
|
|
48
|
+
};
|
|
49
|
+
const args = [];
|
|
50
|
+
if (work) args.push(work);
|
|
51
|
+
args.push(hash2);
|
|
52
|
+
args.push(options);
|
|
53
|
+
response = JSON.stringify(await page.evaluate(async (action2, args2) => {
|
|
54
|
+
if (window.NanoPow == null) throw new Error("NanoPow not found");
|
|
55
|
+
return await window.NanoPow[action2](...args2);
|
|
56
|
+
}, action, args));
|
|
57
|
+
statusCode = 200;
|
|
38
58
|
} catch (err) {
|
|
39
|
-
log(
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
log(err);
|
|
60
|
+
statusCode = 400;
|
|
61
|
+
} finally {
|
|
62
|
+
res.writeHead(statusCode, headers).end(response);
|
|
42
63
|
}
|
|
43
64
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
const server = http.createServer((req, res) => {
|
|
66
|
+
let data = [];
|
|
67
|
+
let reqSize = 0;
|
|
68
|
+
if (req.method === "POST") {
|
|
69
|
+
req.on("data", (chunk) => {
|
|
70
|
+
reqSize += chunk.byteLength;
|
|
71
|
+
if (reqSize > MAX_REQUEST_SIZE) {
|
|
72
|
+
res.writeHead(413, { "Content-Type": "text/plain" });
|
|
73
|
+
res.end("Content Too Large");
|
|
74
|
+
req.socket.destroy();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
data.push(chunk);
|
|
78
|
+
});
|
|
79
|
+
req.on("end", async () => {
|
|
80
|
+
if (!req.socket.destroyed) {
|
|
81
|
+
await respond(res, data);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
86
|
+
res.end(
|
|
87
|
+
`Usage: Send POST request to server URL to generate or validate Nano proof-of-work
|
|
88
|
+
|
|
89
|
+
Generate work for a BLOCKHASH with an optional DIFFICULTY:
|
|
90
|
+
curl -d '{ action: "work_generate", hash: BLOCKHASH, difficulty?: DIFFICULTY }'
|
|
91
|
+
|
|
92
|
+
Validate WORK previously calculated for a BLOCKHASH with an optional DIFFICULTY:
|
|
93
|
+
curl -d '{ action: "work_validate", work: WORK, hash: BLOCKHASH, difficulty?: DIFFICULTY }'
|
|
94
|
+
|
|
95
|
+
BLOCKHASH is a 64-character hexadecimal string.
|
|
96
|
+
WORK is 16-character hexadecimal string.
|
|
97
|
+
DIFFICULTY is a 16-character hexadecimal string (default: FFFFFFF800000000)
|
|
98
|
+
|
|
99
|
+
Report bugs: <bug-nano-pow@zoso.dev>
|
|
100
|
+
Full documentation: <https://www.npmjs.com/package/nano-pow>
|
|
101
|
+
`
|
|
102
|
+
);
|
|
59
103
|
}
|
|
104
|
+
});
|
|
105
|
+
server.on("error", (e) => {
|
|
106
|
+
log("Server error", e);
|
|
60
107
|
try {
|
|
61
|
-
|
|
62
|
-
if (json2.difficulty) options.threshold = BigInt(`0x${json2.difficulty}`);
|
|
63
|
-
return await window.NanoPow.work_validate(json2.work, json2.hash, options);
|
|
64
|
-
}, json, { debug: true, effort: EFFORT });
|
|
65
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
66
|
-
res.end(JSON.stringify(result));
|
|
108
|
+
shutdown();
|
|
67
109
|
} catch (err) {
|
|
68
|
-
log("
|
|
69
|
-
|
|
70
|
-
res.end(JSON.stringify({ error: "work_validate failed" }));
|
|
110
|
+
log("Failed to shut down", err);
|
|
111
|
+
process.exit(1);
|
|
71
112
|
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
84
|
-
page = await browser.newPage();
|
|
85
|
-
page.on("console", (msg) => {
|
|
86
|
-
log(msg.text());
|
|
87
|
-
});
|
|
88
|
-
const path = new URL(import.meta.url).pathname;
|
|
89
|
-
const dir = path.slice(0, path.lastIndexOf("/"));
|
|
90
|
-
await fs.writeFile(`${dir}/server.html`, "");
|
|
91
|
-
await page.goto(import.meta.resolve("./server.html"));
|
|
92
|
-
await page.waitForFunction(async () => {
|
|
93
|
-
return await navigator.gpu.requestAdapter();
|
|
94
|
-
});
|
|
95
|
-
const inject = `${NanoPow};window.NanoPow=NanoPow;`;
|
|
96
|
-
const hash = await crypto.subtle.digest("SHA-256", Buffer.from(inject, "utf-8"));
|
|
97
|
-
const src = `sha256-${Buffer.from(hash).toString("base64")}`;
|
|
98
|
-
await page.setContent(`
|
|
99
|
-
<!DOCTYPE html>
|
|
100
|
-
<head>
|
|
101
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${src}';">
|
|
102
|
-
<script type="module">${inject}</script>
|
|
103
|
-
</head>
|
|
104
|
-
</html>
|
|
105
|
-
`);
|
|
106
|
-
await fs.unlink(`${dir}/server.html`);
|
|
107
|
-
log("Puppeteer initialized");
|
|
108
|
-
const server = http.createServer(async (req, res) => {
|
|
109
|
-
let data = [];
|
|
110
|
-
if (req.method === "POST") {
|
|
111
|
-
req.on("data", (chunk) => {
|
|
112
|
-
data.push(chunk);
|
|
113
|
-
});
|
|
114
|
-
req.on("end", async () => {
|
|
115
|
-
let json;
|
|
116
|
-
try {
|
|
117
|
-
json = JSON.parse(Buffer.concat(data).toString());
|
|
118
|
-
} catch (err) {
|
|
119
|
-
log("JSON.parse error:", err);
|
|
120
|
-
log("Failed JSON:", json);
|
|
121
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
122
|
-
res.end(JSON.stringify({ error: "Invalid data." }));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
switch (json.action) {
|
|
126
|
-
case "work_generate": {
|
|
127
|
-
await work_generate(res, json);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
case "work_validate": {
|
|
131
|
-
await work_validate(res, json);
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
default: {
|
|
135
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
136
|
-
res.end(JSON.stringify({ error: `Invalid data.` }));
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
} else {
|
|
142
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
143
|
-
res.end(serverHelp);
|
|
144
|
-
}
|
|
113
|
+
});
|
|
114
|
+
function shutdown() {
|
|
115
|
+
log("Shutdown signal received");
|
|
116
|
+
const kill = setTimeout(() => {
|
|
117
|
+
log("Server unresponsive, forcefully stopped");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}, 1e4);
|
|
120
|
+
server.close(() => {
|
|
121
|
+
clearTimeout(kill);
|
|
122
|
+
log("Server stopped");
|
|
123
|
+
process.exit(0);
|
|
145
124
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
125
|
+
}
|
|
126
|
+
process.on("SIGINT", shutdown);
|
|
127
|
+
process.on("SIGTERM", shutdown);
|
|
128
|
+
log("Starting NanoPow work server");
|
|
129
|
+
const NanoPow = await readFile(new URL("../main.min.js", import.meta.url), "utf-8");
|
|
130
|
+
browser = await launch({
|
|
131
|
+
headless: true,
|
|
132
|
+
args: [
|
|
133
|
+
"--headless=new",
|
|
134
|
+
"--use-angle=vulkan",
|
|
135
|
+
"--enable-features=Vulkan",
|
|
136
|
+
"--disable-vulkan-surface",
|
|
137
|
+
"--enable-unsafe-webgpu"
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
page = await browser.newPage();
|
|
141
|
+
page.on("console", (msg) => log(msg.text()));
|
|
142
|
+
const path = new URL(import.meta.url).pathname;
|
|
143
|
+
const dir = path.slice(0, path.lastIndexOf("/"));
|
|
144
|
+
const filename = join(dir, `${process.pid}.html`);
|
|
145
|
+
await writeFile(filename, "");
|
|
146
|
+
await page.goto(import.meta.resolve(filename));
|
|
147
|
+
await page.waitForFunction(async () => {
|
|
148
|
+
return await navigator["gpu"].requestAdapter();
|
|
149
|
+
});
|
|
150
|
+
const src = `${NanoPow};window.NanoPow=NanoPow;`;
|
|
151
|
+
const hash = await subtle.digest("SHA-256", Buffer.from(src));
|
|
152
|
+
const enc = `sha256-${Buffer.from(hash).toString("base64")}`;
|
|
153
|
+
await page.setContent(`
|
|
154
|
+
<!DOCTYPE html>
|
|
155
|
+
<head>
|
|
156
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; base-uri 'none'; form-action 'none'; script-src '${enc}';">
|
|
157
|
+
<script type="module">${src}</script>
|
|
158
|
+
</head>
|
|
159
|
+
</html>
|
|
160
|
+
`);
|
|
161
|
+
await unlink(filename);
|
|
162
|
+
log("Puppeteer initialized");
|
|
163
|
+
server.listen(PORT, async () => {
|
|
164
|
+
const ip = await lookup(hostname(), { family: 4 });
|
|
165
|
+
log(`Server process ${process.pid} running at ${ip.address}:${PORT}/`);
|
|
166
|
+
});
|
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/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 gather all results and output them at once 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 1-FFFFFFFFFFFFFFFF.
|
|
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
|
-
`
|