// 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.");