nano-pow
Version:
Proof-of-work generation and validation with WebGPU/WebGL/WASM for Nano cryptocurrency.
380 lines (371 loc) • 11.1 kB
JavaScript
// src/utils/api-support/wasm.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var wasm = { isSupported: false };
Object.defineProperty(wasm, "isSupported", {
get: async function() {
let isWasmSupported = false;
try {
const wasmBuffer = new Uint8Array([
0,
97,
115,
109,
1,
0,
0,
0,
// WASM magic header and version
1,
4,
// Type section, 4 byte descriptor
1,
96,
0,
0,
// 1 type, type function, 0 params, 0 returns
3,
2,
// Function section, 2 byte descriptor
1,
0,
// 1 function, ID 0
10,
23,
// Code section, 23 bytes
1,
21,
// 1 function body, 21 bytes
0,
// 0 local variables
253,
12,
// v128.const
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
// assign i64x2 values
26,
11
// Drop result, end function
]);
const module = await WebAssembly.compile(wasmBuffer);
const instance = await WebAssembly.instantiate(module);
isWasmSupported = instance instanceof WebAssembly.Instance;
} catch (err) {
console.warn("WASM is not supported in this environment.\n", err.message ?? err);
isWasmSupported = false;
} finally {
delete this.isSupported;
this.isSupported = isWasmSupported;
return this.isSupported;
}
}
});
// src/utils/api-support/webgl.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var webgl = { isSupported: false };
Object.defineProperty(webgl, "isSupported", {
get: async function() {
let isWebglSupported = false;
try {
const gl = new OffscreenCanvas(0, 0)?.getContext?.("webgl2");
isWebglSupported = gl instanceof WebGL2RenderingContext && !gl.isContextLost();
} catch (err) {
console.warn("WebGL is not supported in this environment.\n", err);
isWebglSupported = false;
} finally {
delete this.isSupported;
this.isSupported = isWebglSupported;
return this.isSupported;
}
}
});
// src/utils/api-support/webgpu.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var webgpu = { isSupported: false };
Object.defineProperty(webgpu, "isSupported", {
get: async function() {
let isWebgpuSupported = false;
try {
const adapter = await navigator?.gpu?.requestAdapter?.();
const device = await adapter?.requestDevice?.();
isWebgpuSupported = adapter instanceof GPUAdapter && device instanceof GPUDevice;
} catch (err) {
console.warn("WebGPU is not supported in this environment.\n", err);
isWebgpuSupported = false;
} finally {
delete this.isSupported;
this.isSupported = isWebgpuSupported;
return this.isSupported;
}
}
});
// src/utils/api-support/index.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var ApiSupport = { cpu: { isSupported: true }, wasm, webgl, webgpu };
// src/utils/bigint.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
function big(value) {
switch (typeof value) {
case "bigint":
case "boolean":
case "number": {
return BigInt(value);
}
case "string": {
const v = value.trim().replace(/^0[Xx]/, "").replace(/[Nn]$/, "");
if (!isHexN(v, null)) throw TypeError("invalid hex string", { cause: value });
return BigInt(`0x${v}`);
}
default: {
throw TypeError(`can't convert value to BigInt`);
}
}
}
function bigHex(int, length = 0) {
return int.toString(16).padStart(length, "0");
}
function bigToU32(int, length) {
const u32 = new Uint32Array(length);
bigintToUintArray(u32, int);
return u32;
}
function bigToU64(int, length) {
const u64 = new BigUint64Array(length);
bigintToUintArray(u64, int);
return u64;
}
function bigintToUintArray(out, int) {
if (int < 0n) int = ~(int - 1n);
const bits = out instanceof BigUint64Array ? 64n : 32n;
const mask = (1n << bits) - 1n;
for (let i = out.length - 1; i >= 0 && int > 0n; i--) {
const v = int & mask;
out[i] = bits === 64n ? v : Number(v);
int >>= bits;
}
}
// src/utils/cache.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var STORAGE_KEY = "NanoPowCache";
var storage = globalThis.localStorage ?? {
records: /* @__PURE__ */ new Map(),
getItem(key) {
return this.records.get(key);
},
removeItem(key) {
this.records.delete(key);
},
setItem(key, item) {
this.storage.set(key, item);
}
};
function eq(a, b) {
return typeof a === "string" && /^([a-f0-9]{2})+$/i.test(a) && BigInt(`0x${a}`) === b;
}
var Cache = {
clear() {
storage.removeItem(STORAGE_KEY);
},
delete(hash) {
const item = storage.getItem(STORAGE_KEY);
if (item == null) return;
const cache = JSON.parse(item);
for (let i = 0; i < cache.length; i++) {
if (cache[i].hash === hash) {
cache.splice(i, 1);
}
}
storage.setItem(STORAGE_KEY, JSON.stringify(cache));
},
search(hash, difficulty) {
const item = storage.getItem(STORAGE_KEY);
if (item == null) return null;
const cache = JSON.parse(item);
const match = cache.find((c) => c.hash === hash && eq(c.difficulty, difficulty));
return match ?? null;
},
store(result) {
const item = storage.getItem(STORAGE_KEY) ?? "[]";
const cache = JSON.parse(item);
if (cache.push(result) > 1e3) cache.shift();
storage.setItem(STORAGE_KEY, JSON.stringify(cache));
return result;
}
};
// src/utils/logger.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var Logger = class {
isEnabled = false;
groups = {};
groupStart(name) {
if (this.isEnabled) {
console.groupCollapsed(name);
this.groups[name] = true;
}
}
groupEnd(name) {
if (this.groups[name]) {
console.groupEnd();
delete this.groups[name];
}
}
log(...args) {
if (this.isEnabled) {
const datetime = new Date(Date.now()).toLocaleString(Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US", { hour12: false, dateStyle: "medium", timeStyle: "medium" });
for (let i = 0; i < args.length; i++) {
if (typeof args[i] === "string") {
args[i] = args[i].replace(datetime, "").trimStart();
}
if (args[i] instanceof Error) {
if ("stack" in args[i]) {
args.splice(i + 1, 0, args[i].stack);
} else if ("message" in args[i]) {
args.splice(i + 1, 0, args[i].message);
}
}
}
const entry = `${datetime} ${globalThis.process?.title ?? "NanoPow"}[${globalThis.process?.pid ?? "browser"}]:`;
console.log(entry, ...args);
globalThis.process?.send?.({ type: "console", data: `${entry} ${args}` });
}
}
};
// src/utils/queue.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var Queue = class {
#isIdle;
#queue;
constructor() {
this.#isIdle = true;
this.#queue = [];
}
#process = () => {
const { task, resolve, reject, args } = this.#queue.shift() ?? {};
this.#isIdle = !task;
task?.(...args).then(resolve).catch(reject).finally(this.#process);
};
async add(task, ...args) {
if (typeof task !== "function") throw new TypeError("task is not a function");
return new Promise((resolve, reject) => {
this.#queue.push({ task, resolve, reject, args });
if (this.#isIdle) this.#process();
});
}
async prioritize(task, ...args) {
if (typeof task !== "function") throw new TypeError("task is not a function");
return new Promise((resolve, reject) => {
if (typeof task !== "function") reject("task is not a function");
this.#queue.unshift({ task, resolve, reject, args });
if (this.#isIdle) this.#process();
});
}
};
// src/utils/index.ts
//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
var SEND = 0xfffffff800000000n;
var RECEIVE = 0xfffffe0000000000n;
var MAX_HASH = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn;
function isHexN(value, byteLength) {
if (typeof value !== "string") return false;
const length = byteLength === null ? "+" : `{${byteLength << 1}}`;
const r = new RegExp(`^[0-9a-f]${length}$`, "i");
return r.test(value);
}
function isHex8(value) {
return isHexN(value, 8);
}
function isHex32(value) {
return isHexN(value, 32);
}
function stats(times) {
if (times == null || times.length === 0) return null;
const count = times.length;
const truncatedStart = Math.floor(count * 0.1);
const truncatedEnd = count - truncatedStart;
const truncatedCount = truncatedEnd - truncatedStart;
let min = Number.MAX_SAFE_INTEGER;
let logarithms, max, median, reciprocals, total;
logarithms = max = median = reciprocals = total = 0;
let truncatedMin = Number.MAX_SAFE_INTEGER;
let truncatedLogarithms, truncatedMax, truncatedReciprocals, truncatedTotal;
truncatedLogarithms = truncatedMax = truncatedReciprocals = truncatedTotal = 0;
times.sort((a, b) => a - b);
for (let i = 0; i < count; i++) {
const time = times[i];
total += time;
logarithms += Math.log(time);
reciprocals += 1 / time;
min = Math.min(min, time);
max = Math.max(max, time);
if (i === Math.floor((count - 1) / 2)) median = time;
if (i === Math.floor(count / 2) && count % 2 === 0) median = (median + time) / 2;
}
for (let i = truncatedStart; i < truncatedEnd; i++) {
const time = times[i];
truncatedTotal += time;
truncatedLogarithms += Math.log(time);
truncatedReciprocals += 1 / time;
truncatedMin = Math.min(truncatedMin, time);
truncatedMax = Math.max(truncatedMax, time);
}
return {
count,
total,
rate: 1e3 * count / total,
min,
max,
median,
arithmetic: total / count,
geometric: Math.exp(logarithms / count),
harmonic: count / reciprocals,
truncatedCount,
truncatedTotal,
truncatedRate: 1e3 * truncatedCount / truncatedTotal,
truncatedMin,
truncatedMax,
truncatedArithmetic: truncatedTotal / truncatedCount,
truncatedGeometric: Math.exp(truncatedLogarithms / truncatedCount),
truncatedHarmonic: truncatedCount / truncatedReciprocals
};
}
export {
ApiSupport,
Cache,
Logger,
MAX_HASH,
Queue,
RECEIVE,
SEND,
big,
bigHex,
bigToU32,
bigToU64,
isHex32,
isHex8,
isHexN,
stats
};
//# sourceMappingURL=index.js.map