diff --git a/demo/index.html b/demo/index.html
index 56e1f56..bf915ff 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -86,6 +86,9 @@
note⛏️
+
+
+
Hash Rate:
0 H/s
Result:
diff --git a/demo/main.js b/demo/main.js
index cd82958..9b68849 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -11,27 +11,51 @@ const pubkey = window.NostrTools.getPublicKey(secret);
const pool = new SimplePool();
let pubs = [];
-let NUM_WORKERS = navigator.hardwareConcurrency-1 || 2; // Or set a fixed number
+let totalWorkers = navigator.hardwareConcurrency-1 || 2; // Or set a fixed number
let workers = [];
let isWorkerReady = 0;
let isMining = false;
const mineButton = document.getElementById('mineButton');
const eventInput = document.getElementById('eventInput');
+
const difficultyInput = document.getElementById('difficulty');
-const resultOutput = document.getElementById('result');
-const hashrateOutput = document.getElementById('hashrate');
+const numberOfWorkers = document.getElementById('numberOfWorkers')
+
const cancelButton = document.getElementById('cancelButton');
const relayStatus = document.getElementById('relayStatus');
+
+const minersBestPowOutput = document.getElementById('minersBestPow');
+const overallBestPowOutput = document.getElementById('overallBestPow');
+const hashrateOutput = document.getElementById('hashrate');
+
+const resultOutput = document.getElementById('result');
const neventOutput = document.getElementById('neventOutput');
-const numberOfWorkers = document.getElementById('numberOfWorkers');
-numberOfWorkers.value = NUM_WORKERS;
+
+numberOfWorkers.value = totalWorkers;
numberOfWorkers.max = navigator.hardwareConcurrency || 3;
+minersBestPowOutput.style.display = 'none';
+overallBestPowOutput.style.display = 'none';
+neventOutput.style.display = 'none';
+relayStatus.style.display = 'none';
+
let workerHashRates = {};
+let minersBestPow
+let overallBestPow
+
+function resetBestPow() {
+ minersBestPow = {};
+ overallBestPow = {
+ bestPow: 0,
+ nonce: 0,
+ hash: '',
+ workerId: null,
+ };
+}
-for (let i = 0; i < NUM_WORKERS; i++) {
+for (let i = 0; i < totalWorkers; i++) {
const worker = new Worker('./worker.js', { type: 'module' });
worker.onmessage = handleWorkerMessage;
worker.postMessage({ type: 'init', id: i });
@@ -39,15 +63,36 @@ for (let i = 0; i < NUM_WORKERS; i++) {
}
function handleWorkerMessage(e) {
- const { type, data, error, hashRate, workerId } = e.data;
+ const { type, data, error, hashRate, workerId, bestPowData:bestPowDataMap } = e.data;
if (type === 'progress') {
+
workerHashRates[workerId] = hashRate;
const totalHashRate = Object.values(workerHashRates).reduce((a, b) => a + b, 0);
hashrateOutput.textContent = `${(totalHashRate / 1000).toFixed(2)} kH/s`;
+
+ if (bestPowDataMap?.size > 0) {
+ const bestPowData = Object.fromEntries(bestPowDataMap);
+ const { best_pow, nonce, hash } = bestPowData;
+ minersBestPow[workerId] = {
+ bestPow: best_pow,
+ nonce,
+ hash,
+ };
+ if (best_pow > overallBestPow.bestPow) {
+ overallBestPow = {
+ bestPow: best_pow,
+ nonce,
+ hash,
+ workerId,
+ };
+ }
+ updateBestPowDisplay();
+ }
+
} else if (type === 'ready') {
isWorkerReady++;
- if (isWorkerReady === NUM_WORKERS) {
+ if (isWorkerReady === totalWorkers) {
console.log('All workers are ready.');
mineButton.disabled = false;
resultOutput.textContent = 'Workers are ready. You can start mining.';
@@ -61,6 +106,7 @@ ${JSON.stringify(data, null, 2)}
} else {
try {
resultOutput.textContent = JSON.stringify(data, null, 2);
+ neventOutput.style.display = 'block';
publishEvent(data.event);
cancelOtherWorkers(workerId);
} catch (e) {
@@ -91,15 +137,31 @@ function cancelOtherWorkers(excludeWorkerId) {
});
}
+function updateBestPowDisplay() {
+ // Update the UI to display each miner's best PoW
+ let minersPowInfo = '';
+ for (const [workerId, powData] of Object.entries(minersBestPow)) {
+ minersPowInfo += `Miner #${workerId}: Best PoW ${powData.bestPow} (Nonce: ${powData.nonce}, Hash: ${powData.hash})\n`;
+ }
+ minersBestPowOutput.textContent = minersPowInfo;
+ // Update the UI to display the overall best PoW
+ if (overallBestPow.workerId !== null) {
+ overallBestPowOutput.textContent = `Overall Best PoW: ${overallBestPow.bestPow} by Miner #${overallBestPow.workerId} (Nonce: ${overallBestPow.nonce}, Hash: ${overallBestPow.hash})`
+ }
+}
mineButton.addEventListener('click', () => {
if (isMining) return;
+ resetBestPow();
+ minersBestPowOutput.style.display = 'block';
+ overallBestPowOutput.style.display = 'block';
+
const content = eventInput.value.trim();
const nostrEvent = generateEvent(content);
const difficulty = parseInt(difficultyInput.value, 10);
- const NUM_WORKERS = parseInt(numberOfWorkers.value, 10);
+ const totalWorkers = parseInt(numberOfWorkers.value, 10);
relayStatus.textContent = '';
neventOutput.textContent = '';
@@ -116,7 +178,7 @@ mineButton.addEventListener('click', () => {
return;
}
- if (isWorkerReady < NUM_WORKERS) {
+ if (isWorkerReady < totalWorkers) {
alert('Workers are not ready yet. Please wait.');
return;
}
@@ -129,12 +191,15 @@ mineButton.addEventListener('click', () => {
hashrateOutput.textContent = '0 H/s';
isMining = true;
+ console.log('main: event:', event)
+
workers.forEach((worker, index) => {
worker.postMessage({
type: 'mine',
event,
difficulty: difficulty,
workerId: index,
+ totalWorkers,
});
});
});
@@ -210,6 +275,7 @@ const publishEvent = async (ev) => {
if (!isGood) throw new Error('Event is not valid');
pubs = pool.publish(RELAYS, ev);
await Promise.allSettled(pubs);
+ relayStatus.style.display = '';
showRelayStatus();
console.log('Event published successfully.');
neventOutput.textContent = generateNEvent(ev);
@@ -262,19 +328,19 @@ const showRelayStatus = () => {
// const neventOutput = document.getElementById('neventOutput');
// const numberOfWorkers = document.getElementById('numberOfWorkers');
-// let NUM_WORKERS = navigator.hardwareConcurrency || 4; // Or set a fixed number
+// let totalWorkers = navigator.hardwareConcurrency || 4; // Or set a fixed number
// let workers = [];
// let isWorkerReady = 0;
// let isMining = false;
// let pubs = [];
-// numberOfWorkers.value = NUM_WORKERS;
+// numberOfWorkers.value = totalWorkers;
// const MOVING_AVERAGE_WINDOW = 2;
// let recentHashRates = [];
-// for (let i = 0; i < NUM_WORKERS; i++) {
+// for (let i = 0; i < totalWorkers; i++) {
// const worker = new Worker('./worker.js', { type: 'module' });
// worker.onmessage = handleWorkerMessage;
// worker.postMessage({ type: 'init' });
@@ -286,14 +352,14 @@ const showRelayStatus = () => {
// if (type === 'progress') {
// recentHashRates.push(averageHashRate);
-// if (recentHashRates.length > MOVING_AVERAGE_WINDOW * NUM_WORKERS) {
+// if (recentHashRates.length > MOVING_AVERAGE_WINDOW * totalWorkers) {
// recentHashRates.shift();
// }
// const totalHashRate = recentHashRates.reduce((a, b) => a + b, 0);
// hashrateOutput.textContent = `${(totalHashRate / 1000).toFixed(2)} kH/s`;
// } else if (type === 'ready') {
// isWorkerReady++;
-// if (isWorkerReady === NUM_WORKERS) {
+// if (isWorkerReady === totalWorkers) {
// console.log('All workers are ready.');
// mineButton.disabled = false;
// resultOutput.textContent = 'Workers are ready. You can start mining.';
@@ -339,7 +405,7 @@ const showRelayStatus = () => {
// const content = eventInput.value.trim();
// const nostrEvent = generateEvent(content);
// const difficulty = parseInt(difficultyInput.value, 10);
-// NUM_WORKERS = parseInt(numberOfWorkers.value, 10);
+// totalWorkers = parseInt(numberOfWorkers.value, 10);
// relayStatus.textContent = '';
// neventOutput.textContent = '';
@@ -356,7 +422,7 @@ const showRelayStatus = () => {
// return;
// }
-// if (isWorkerReady < NUM_WORKERS) {
+// if (isWorkerReady < totalWorkers) {
// alert('Workers are not ready yet. Please wait.');
// return;
// }
diff --git a/demo/worker.js b/demo/worker.js
index cf29e13..1a2672c 100644
--- a/demo/worker.js
+++ b/demo/worker.js
@@ -14,27 +14,48 @@ async function initWasm() {
}
}
-function reportProgress(hashRate) {
- postMessage({ type: 'progress', hashRate, workerId });
-}
+
function shouldCancel() {
return miningCancelled;
}
self.onmessage = async function (e) {
- const { type, event, difficulty, id } = e.data;
+ const { type, event, difficulty, workerId, totalWorkers } = e.data;
+
+ function reportProgress(hashRate, bestPowData) {
+
+
+ const message = {
+ type: 'progress',
+ hashRate,
+ workerId,
+ };
+
+ if (bestPowData && bestPowData !== null) {
+ message.bestPowData = bestPowData;
+ }
+
+ postMessage(message);
+ }
+
if (type === 'init') {
- workerId = id;
initWasm();
} else if (type === 'mine' && !mining) {
miningCancelled = false; // Reset cancellation flag
mining = true;
try {
- if (typeof event !== 'string') {
- throw new Error('Event must be a stringified JSON.');
- }
- const minedResult = mine_event(event, difficulty, reportProgress, shouldCancel);
+ const startNonce = BigInt(workerId);
+ const nonceStep = BigInt(totalWorkers);
+
+ const minedResult = mine_event(
+ event,
+ difficulty,
+ startNonce.toString(),
+ nonceStep.toString(),
+ reportProgress,
+ shouldCancel
+ );
postMessage({ type: 'result', data: minedResult, workerId });
} catch (error) {
if (error.message !== 'Mining cancelled.') {
diff --git a/src/lib.rs b/src/lib.rs
index f05c5d6..c338bf5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@ use wasm_bindgen::prelude::*;
use web_sys::console;
use console_error_panic_hook;
use js_sys::Function;
+use serde_wasm_bindgen;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NostrEvent {
@@ -85,10 +86,12 @@ pub fn main_js() {
pub fn mine_event(
event_json: &str,
difficulty: u32,
+ start_nonce_str: &str,
+ nonce_step_str: &str,
report_progress: JsValue,
should_cancel: JsValue,
) -> JsValue {
-
+ console::log_1(&format!("event_json: {}", event_json).into());
let mut event: NostrEvent = match serde_json::from_str(event_json) {
Ok(e) => e,
Err(err) => {
@@ -113,7 +116,11 @@ pub fn mine_event(
}
}
if nonce_index.is_none() {
- event.tags.push(vec!["nonce".to_string(), "0".to_string(), difficulty.to_string()]);
+ event.tags.push(vec![
+ "nonce".to_string(),
+ "0".to_string(),
+ difficulty.to_string(),
+ ]);
nonce_index = Some(event.tags.len() - 1);
}
@@ -129,13 +136,20 @@ pub fn mine_event(
};
let start_time = js_sys::Date::now();
- let mut nonce: u64 = 0;
+ let start_nonce: u64 = start_nonce_str.parse().unwrap_or(0);
+ let nonce_step: u64 = nonce_step_str.parse().unwrap_or(1);
+
+ let mut nonce: u64 = start_nonce;
let mut total_hashes: u64 = 0;
let report_interval = 200_000;
let mut last_report_time = start_time;
let should_cancel = should_cancel.dyn_into::().ok();
+ let mut best_pow: u32 = 0;
+ let mut best_nonce: u64 = 0;
+ let mut best_hash_bytes: Vec = Vec::new();
+
loop {
if let Some(index) = nonce_index {
if let Some(tag) = event.tags.get_mut(index) {
@@ -157,7 +171,30 @@ pub fn mine_event(
let pow = get_pow(&hash_bytes);
- total_hashes += 1;
+ if pow > best_pow {
+ best_pow = pow;
+ best_nonce = nonce;
+ best_hash_bytes = hash_bytes.clone();
+
+ let best_pow_data = serde_json::json!({
+ "best_pow": best_pow,
+ "nonce": best_nonce.to_string(),
+ "hash": hex::encode(&best_hash_bytes),
+ });
+
+ report_progress
+ .call2(
+ &JsValue::NULL,
+ &JsValue::from_f64(0.0),
+ &serde_wasm_bindgen::to_value(&best_pow_data).unwrap(),
+ )
+ .unwrap_or_else(|err| {
+ console::log_1(
+ &format!("Error calling progress callback: {:?}", err).into(),
+ );
+ JsValue::NULL
+ });
+ }
if pow >= difficulty {
let event_hash = hex::encode(&hash_bytes);
@@ -176,10 +213,11 @@ pub fn mine_event(
return serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL);
}
- nonce += 1;
+ nonce = nonce.wrapping_add(nonce_step);
+ total_hashes += 1;
if let Some(ref should_cancel) = should_cancel {
- if nonce % 10_000 == 0 {
+ if total_hashes % 10_000 == 0 {
let cancel = should_cancel.call0(&JsValue::NULL).unwrap_or(JsValue::FALSE);
if cancel.is_truthy() {
console::log_1(&"Mining cancelled.".into());
@@ -191,13 +229,13 @@ pub fn mine_event(
}
}
- if nonce % report_interval == 0 {
+ if total_hashes % report_interval == 0 {
let current_time = js_sys::Date::now();
let elapsed_time = (current_time - last_report_time) / 1000.0;
if elapsed_time > 0.0 {
let hash_rate = (report_interval as f64) / elapsed_time;
report_progress
- .call1(&JsValue::NULL, &hash_rate.into())
+ .call2(&JsValue::NULL, &hash_rate.into(), &JsValue::NULL)
.unwrap_or_else(|err| {
console::log_1(
&format!("Error calling progress callback: {:?}", err).into(),
@@ -208,6 +246,7 @@ pub fn mine_event(
}
}
+ // Uncomment if you wish to log nonce progress
// if nonce % report_interval == 0 {
// console::log_1(&format!("Checked nonce up to: {}", nonce).into());
// }
@@ -215,7 +254,6 @@ pub fn mine_event(
}
-
#[cfg(test)]
mod tests {
use super::*;