personal_website/public/emscripten-worker.js

390 lines
12 KiB
JavaScript

// This is the dedicated Emscripten worker script.
const messageQueue = [];
// Define the Module object for Emscripten
var Module = { // Use a different name to avoid confusion
locateFile: (path) => {
console.log(`EmscriptenWorker: locateFile called for path: ${path}`);
if (path.endsWith('.wasm')) {
console.log("EmscriptenWorker: Returning /web-randomx.wasm for WASM file.");
return '/web-randomx.wasm'; // WASM file is in public/
}
return path;
},
onRuntimeInitialized: () => {
console.log("EmscriptenWorker: Emscripten runtime initialized.");
try {
hashingLogic = new HashingWrapper(Module); // Module is now the actual Emscripten module
console.log("EmscriptenWorker: HashingWrapper instantiated.");
self.postMessage({ type: 'emscripten-initialized' }); // Notify parent worker
// Process any queued messages
while (messageQueue.length > 0) {
processMessage(messageQueue.shift());
}
} catch (e) {
console.error("EmscriptenWorker: Error instantiating HashingWrapper:", e);
}
}
};
console.log("EmscriptenWorker: Emscripten module config defined.");
// Import the Emscripten-generated web-randomx.js
try {
self.importScripts('/web-randomx.js');
console.log("EmscriptenWorker: web-randomx.js imported successfully.");
} catch (e) {
console.error("EmscriptenWorker: Error importing or executing web-randomx.js:", e);
}
let hashingLogic = null;
class HashingWrapper {
constructor(module) {
this.module = module;
// Available exported functions from WASM
this.initCacheFunc = this.module._web_randomx_init_cache;
this.createVmFunc = this.module._web_randomx_create_vm;
this.hashFunc = this.module._web_randomx_hash;
this.releaseCacheFunc= this.module._web_randomx_release_cache;
this.destroyVmFunc = this.module._web_randomx_destroy_vm;
const exportedKeys = Object.keys(this.module).filter(key =>
key.startsWith('_web') || key.startsWith('_randomx') || key.startsWith('_')
);
console.log("EmscriptenWorker: DIAGNOSIS - Available exported keys:", exportedKeys);
if (!this.initCacheFunc || !this.createVmFunc || !this.hashFunc) {
console.error("EmscriptenWorker: CRITICAL: Required functions not found. Hashing will fail.");
}
// Internal state
this.currentJob = null;
this.throttleWait = 0;
this.throttledStart = 0;
this.throttledHashes = 0;
this.workThrottledBound = this.workThrottled.bind(this);
this.target = new Uint8Array(32);
this.input = null;
this.output = null;
this.seed_input = null;
this.blob = null;
this.seed_blob = null;
this.variant = 0;
this.height = 0;
this.isWorking = false;
}
allocateMemory() {
if (this.input && this.input.byteLength > 0) return;
try {
const mallocFunc = this.module._malloc || this.module.malloc;
if (this.module.HEAPU8 && this.module.HEAPU8.buffer && mallocFunc) {
// Allocate space for the job blob (256 bytes is a common max)
this.input = new Uint8Array(this.module.HEAPU8.buffer, mallocFunc(256), 256);
// Allocate space for the 32-byte hash output
this.output = new Uint8Array(this.module.HEAPU8.buffer, mallocFunc(32), 32);
// Allocate space for the 32-byte seed hash input
this.seed_input = new Uint8Array(this.module.HEAPU8.buffer, mallocFunc(32), 32);
if (this.input.byteOffset === 0) {
console.error("EmscriptenWorker: Malloc returned 0. Allocation failed.");
this.input = null;
}
} else {
console.error(`EmscriptenWorker: Memory allocation failed. _malloc is missing.`);
}
} catch (e) {
console.error("EmscriptenWorker: Alloc error:", e);
}
}
hexToBytes(hex) {
const len = hex.length / 2;
let bytes = new Uint8Array(len);
for (let i = 0; i < len; ++i) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return bytes;
}
bytesToHex(bytes) {
let hex = '';
for (let i = 0; i < bytes.length; ++i) {
hex += (bytes[i] >>> 4).toString(16);
hex += (15 & bytes[i]).toString(16);
}
return hex;
}
meetsTarget(output, target) {
let isZero = true;
for (let j = 0; j < output.length; j++) {
if (output[j] !== 0) {
isZero = false;
break;
}
}
if (isZero) return false;
for (let i = 1; i <= target.length; ++i) {
if (output[output.length - i] > target[target.length - i]) return false;
if (output[output.length - i] < target[target.length - i]) return true;
}
return false;
}
setJob(data) {
console.log('EmscriptenWorker: Setting new job:', data);
this.allocateMemory();
if (!this.input) {
console.error("EmscriptenWorker: setJob aborted, memory not allocated.");
return;
}
try {
this.currentJob = data;
this.blob = this.hexToBytes(data.blob);
this.input.set(this.blob);
const targetBytes = this.hexToBytes(data.target);
if (targetBytes.length <= 8) {
for (let i = 1; i <= targetBytes.length; ++i) {
this.target[this.target.length - i] = targetBytes[targetBytes.length - i];
}
for (let i = 0; i < this.target.length - targetBytes.length; ++i) {
this.target[i] = 255;
}
} else {
this.target.set(targetBytes);
}
this.variant = data.variant === undefined ? 0 : data.variant;
this.height = data.height === undefined ? 0 : data.height;
this.seed_blob = this.hexToBytes(data.seed_hash);
this.seed_input.set(this.seed_blob);
if (this.initCacheFunc) {
try {
console.log('EmscriptenWorker: Initializing cache.');
this.initCacheFunc(this.variant, BigInt(this.height), this.seed_input.byteOffset);
console.log('EmscriptenWorker: Cache initialized.');
} catch (initError) {
console.error("EmscriptenWorker: RandomX VM initialization failed in WASM (setJob).", initError);
}
} else {
console.error("EmscriptenWorker: Critical: Initialization function not found. Hashing will fail.");
}
} catch (e) {
console.error("Job set error:", e);
}
}
now() {
return (self.performance ? self.performance.now() : Date.now());
}
hash(input, output, byteLength, variant, height, seed) {
if (!this.input || this.input.byteLength === 0 || !this.hashFunc) return 0;
try {
const nonce = 4294967295 * Math.random() + 1 >>> 0;
this.input[39] = (4278190080 & nonce) >> 24;
this.input[40] = (16711680 & nonce) >> 16;
this.input[41] = (65280 & nonce) >> 8;
this.input[42] = (255 & nonce) >> 0;
this.hashFunc(this.variant, BigInt(this.height), seed.byteOffset, input.byteOffset, byteLength, output.byteOffset);
return 1;
} catch (e) {
// The crash is due to uninitialized VM, which is fixed by recompiling (Step 1)
console.error('EmscriptenWorker: Error during hash calculation:', e);
return 0;
}
}
work() {
if (!this.isWorking || !this.currentJob) {
console.log('EmscriptenWorker: Work loop stopped. isWorking:', this.isWorking, 'currentJob:', this.currentJob);
return;
}
this.allocateMemory();
if (!this.input) {
setTimeout(() => this.work(), 100);
return;
}
const workStart = this.now();
let hashes = 0;
let ifMeetTarget = false;
let interval = 0;
let loopCount = 0;
while (!ifMeetTarget && interval < 1000 && loopCount < 100000) {
hashes += this.hash(this.input, this.output, this.blob.length, this.variant, this.height, this.seed_input);
ifMeetTarget = this.meetsTarget(this.output, this.target);
interval = this.now() - workStart;
loopCount++;
}
const effectiveInterval = interval > 0 ? interval : 1;
const hashesPerSecond = hashes / (effectiveInterval / 1e3);
if (ifMeetTarget) {
const nonce = this.bytesToHex(this.input.subarray(39, 43));
const result = this.bytesToHex(this.output);
self.postMessage({
type: 'hash-found',
payload: {
hashesPerSecond: hashesPerSecond,
hashes: hashes,
job_id: this.currentJob.job_id,
nonce: nonce,
result: result
}
});
} else {
self.postMessage({
type: 'hash-stats',
payload: {
hashesPerSecond: hashesPerSecond,
hashes: hashes
}
});
}
if (this.isWorking) {
setTimeout(() => this.work(), 0);
}
}
workThrottled() {
console.log('EmscriptenWorker: Starting throttled work loop.');
if (!this.isWorking || !this.currentJob) {
console.log('EmscriptenWorker: Throttled work loop stopped. isWorking:', this.isWorking, 'currentJob:', this.currentJob);
return;
}
this.allocateMemory();
if (!this.input) {
setTimeout(this.workThrottledBound, 100);
return;
}
const WORK_BURST_MS = 50;
const throttleRatio = 1 / (1 - this.throttleWait) - 1;
const SLEEP_TIME_MS = WORK_BURST_MS * throttleRatio;
const burstStart = this.now();
if (this.throttledStart === 0) this.throttledStart = burstStart;
let hashesInBurst = 0;
let targetFound = false;
while ((this.now() - burstStart) < WORK_BURST_MS) {
hashesInBurst += this.hash(this.input, this.output, this.blob.length, this.variant, this.height, this.seed_input);
if (this.meetsTarget(this.output, this.target)) {
targetFound = true;
break;
}
}
this.throttledHashes += hashesInBurst;
const totalInterval = this.now() - this.throttledStart;
const effectiveTotal = totalInterval > 0 ? totalInterval : 1;
const hashesPerSecond = this.throttledHashes / (effectiveTotal / 1e3);
if (targetFound) {
const nonce = this.bytesToHex(this.input.subarray(39, 43));
const result = this.bytesToHex(this.output);
self.postMessage({
type: 'hash-found',
payload: {
hashesPerSecond: hashesPerSecond,
hashes: this.throttledHashes,
job_id: this.currentJob.job_id,
nonce: nonce,
result: result
}
});
this.throttledHashes = 0;
this.throttledStart = 0;
setTimeout(this.workThrottledBound, 0);
} else if (totalInterval > 1000) {
self.postMessage({
type: 'hash-stats',
payload: {
hashesPerSecond: hashesPerSecond,
hashes: this.throttledHashes
}
});
// TYPO FIX: Changed 'thisottledHashes' to 'this.throttledHashes'
this.throttledHashes = 0;
this.throttledStart = 0;
setTimeout(this.workThrottledBound, 0);
} else {
const delay = Math.max(1, SLEEP_TIME_MS);
setTimeout(this.workThrottledBound, delay);
}
}
}
// This worker will receive messages from its parent worker (miner.worker.js)
function processMessage(data) {
const { type, payload } = data;
console.log('EmscriptenWorker: Processing message:', data);
if (!hashingLogic) {
console.warn("EmscriptenWorker: Hashing logic not yet initialized, queueing message.");
messageQueue.push(data);
return;
}
switch (type) {
case 'set-job':
hashingLogic.setJob(payload.job);
hashingLogic.throttleWait = 1 / (1 - payload.throttle) - 1; // Set throttle
hashingLogic.isWorking = true;
hashingLogic.work();
break;
case 'start-work':
hashingLogic.isWorking = true;
hashingLogic.work();
break;
case 'start-throttled-work':
hashingLogic.isWorking = true;
hashingLogic.workThrottled();
break;
case 'stop-hashing':
hashingLogic.isWorking = false;
break;
case 'set-throttle':
hashingLogic.throttleWait = 1 / (1 - payload) - 1;
break;
case 'set-threads':
// Emscripten module might not directly support setting threads from here
console.warn("EmscriptenWorker: set-threads not directly supported by HashingWrapper.");
break;
default:
console.log("EmscriptenWorker: Unknown message type", type);
break;
}
}
self.addEventListener('message', (event) => {
processMessage(event.data);
});
console.log("EmscriptenWorker: Script loaded.");