Single hash, new poseidon (#50)

* single hash, new poseidon

* fix build

* new poseidon api

* fmt

* fix race condition with stale jobs being reported

* fmt

* fix deps

* format toml

remove stale comments

---------

Co-authored-by: Nikolaus Heger <nheger@gmail.com>
This commit is contained in:
illuzen
2026-04-02 14:06:54 +08:00
committed by GitHub
co-authored by Nikolaus Heger
parent 443f0f9f89
commit 417a3efd86
9 changed files with 588 additions and 405 deletions
Generated
+454 -276
View File
File diff suppressed because it is too large Load Diff
+4 -8
View File
@@ -29,19 +29,15 @@ num-bigint = { version = "0.4", features = ["rand"] }
num-traits = "0.2"
num_cpus = "1.16"
primitive-types = { version = "0.13.1", default-features = false }
qp-poseidon-core = { version = "1.0.1", default-features = false }
qpow-math = { git = "https://github.com/Quantus-Network/chain.git", package = "qpow-math", default-features = false }
# Point to local chain repo for development; switch back to crates.io version for release
quantus-miner-api = { git = "https://github.com/Quantus-Network/chain.git", branch = "illuzen/quic-miner" }
qp-poseidon-core = { version = "1.4.0", default-features = false }
qpow-math = { git = "https://github.com/Quantus-Network/chain.git", tag = "v0.4.11-star-dust", package = "qpow-math", default-features = false }
quantus-miner-api = { git = "https://github.com/Quantus-Network/chain.git", tag = "v0.4.11-star-dust" }
rand = { version = "0.8.5", default-features = false }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0.132", default-features = false }
thiserror = "1"
tokio = { version = "1.36", features = ["full"] }
warp = "0.3" # Used by metrics crate for Prometheus HTTP endpoint
# pow-core now delegates to qpow-math for core functionality while providing optimized mining scaffolding.
# GPU backend dependencies are declared in individual engine crates (no optional deps in workspace)
warp = "0.3"
[profile.release]
opt-level = 3
+4 -2
View File
@@ -22,10 +22,12 @@ engine-cpu = { path = "../engine-cpu" }
pow-core = { path = "../pow-core" }
primitive-types = { workspace = true }
log = { workspace = true }
qp-poseidon-core = { version = "1.0.2", features = ["p2"] }
qp-poseidon-constants = { version = "1.0.2" }
qp-poseidon-core = { workspace = true }
qp-poseidon-constants = "1.1"
qp-plonky2-field = { version = "1.1.1" }
plonky2 = { package = "qp-plonky2", version = "1.1.3" }
p3-goldilocks = "0.3"
p3-field = "0.3"
wgpu = { version = "27.0.1" } # GPU compute library
futures = "0.3" # For async executor
+2 -2
View File
@@ -93,8 +93,8 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("❌ Poseidon2 squeeze-twice tests failed: {}", e);
}
if let Err(e) = tests::test_double_hash(&device, &queue).await {
eprintln!("❌ Double hash tests failed: {}", e);
if let Err(e) = tests::test_hash_squeeze_twice(&device, &queue).await {
eprintln!("❌ Hash squeeze twice tests failed: {}", e);
}
if let Err(e) = end_to_end_tests::test_end_to_end_mining(&device, &queue).await {
+16 -18
View File
@@ -9,7 +9,7 @@ const GOLDILOCKS_PRIME_HIGH: u32 = 4294967295u; // High 32 bits (2^32 - 1)
// Poseidon2 constants
const WIDTH: u32 = 12u;
const RATE: u32 = 4u;
const RATE: u32 = 8u;
const EXTERNAL_ROUNDS: u32 = 4u;
const INTERNAL_ROUNDS: u32 = 22u;
@@ -677,17 +677,17 @@ fn poseidon2_hash_squeeze_twice(input: array<u32, 24>) -> array<u32, 16> {
state[i] = gf_zero();
}
// Convert input to field elements (25 total)
// Convert input to field elements (25 total: 24 from input + 1 terminator)
let input_felts = bytes_to_field_elements(input);
// Sponge construction matching CPU reference exactly:
// CPU processes field elements using push_to_buf() which absorbs in chunks of RATE=4
// CPU processes field elements using push_to_buf() which absorbs in chunks of RATE=8
// Process first 24 elements (6 complete chunks of 4)
for (var chunk = 0u; chunk < 6u; chunk++) {
// Absorb 4 elements for this chunk
for (var i = 0u; i < 4u; i++) {
let felt_idx = chunk * 4u + i;
// Process first 24 elements (3 complete chunks of 8)
for (var chunk = 0u; chunk < 3u; chunk++) {
// Absorb 8 elements for this chunk
for (var i = 0u; i < 8u; i++) {
let felt_idx = chunk * 8u + i;
state[i] = gf_add(state[i], input_felts[felt_idx]);
}
// Permute after each complete chunk
@@ -695,17 +695,16 @@ fn poseidon2_hash_squeeze_twice(input: array<u32, 24>) -> array<u32, 16> {
}
// Process remaining 1 element (partial chunk)
// Now we have element 24 (the padding marker = 1) remaining
// This simulates CPU's finalize_twice adding ONE to buffer position 0
state[0] = gf_add(state[0], input_felts[24u]); // Add the padding marker (should be 1)
// Now we have element 24 (the terminator = 1) remaining
state[0] = gf_add(state[0], input_felts[24u]); // Add the terminator (value 1)
// Add sponge padding marker (ONE) to the next position
state[1] = gf_add(state[1], gf_one());
// Final permutation (CPU calls permute after completing the block)
// Final permutation
poseidon2_permute(&state);
// First squeeze - get first 4 field elements
// First squeeze - get first 4 field elements (POSEIDON2_OUTPUT=4 -> 32 bytes)
let first_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
@@ -784,10 +783,9 @@ fn poseidon2_hash_squeeze_twice_64(input: array<u32, 16>) -> array<u32, 16> {
return result;
}
// Double Poseidon2 hash (like Bitcoin's double SHA256)
fn double_hash(input: array<u32, 24>) -> array<u32, 16> {
let first_hash = poseidon2_hash_squeeze_twice(input);
return poseidon2_hash_squeeze_twice_64(first_hash);
// Single Poseidon2 hash with double squeeze (512-bit output)
fn hash_squeeze_twice(input: array<u32, 24>) -> array<u32, 16> {
return poseidon2_hash_squeeze_twice(input);
}
// Check if hash < target (U512 comparison)
@@ -886,7 +884,7 @@ fn mining_main(@builtin(global_invocation_id) global_id: vec3<u32>) {
}
// Hash (Big Endian)
let hash_be = double_hash(input);
let hash_be = hash_squeeze_twice(input);
// Convert to Little Endian for difficulty check and storage
var hash_le: array<u32, 16>;
+24 -34
View File
@@ -1,8 +1,8 @@
use p3_field::PrimeField64 as P3PrimeField64;
use p3_goldilocks::Goldilocks as P3Goldilocks;
use plonky2::hash::poseidon2::P2Permuter;
use qp_plonky2_field::goldilocks_field::GoldilocksField;
use qp_plonky2_field::types::{Field, Field64, PrimeField64};
use qp_poseidon_core::serialization::{p2_backend::GF as P2Goldilocks, GoldiCompat};
use rand::{Rng, SeedableRng};
use wgpu::util::DeviceExt;
@@ -888,13 +888,17 @@ fn generate_bytes_to_field_test_vectors() -> Vec<BytesToFieldTestCase> {
let mut vectors = Vec::new();
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0x98765432);
// Helper to convert P3 Goldilocks to P2 (Plonky2) GoldilocksField
fn p3_to_p2(felts: Vec<P3Goldilocks>) -> Vec<GoldilocksField> {
felts
.into_iter()
.map(|g| GoldilocksField::from_noncanonical_u64(g.as_canonical_u64()))
.collect()
}
// Test 1: All zeros
let zero_input = [0u8; 96];
let zero_expected =
qp_poseidon_core::serialization::injective_bytes_to_felts::<P2Goldilocks>(&zero_input)
.into_iter()
.map(|g| GoldilocksField::from_noncanonical_u64(g.to_u64()))
.collect();
let zero_expected = p3_to_p2(qp_poseidon_core::serialization::bytes_to_felts(&zero_input));
vectors.push(BytesToFieldTestCase {
input_bytes: zero_input,
expected_felts: zero_expected,
@@ -902,11 +906,7 @@ fn generate_bytes_to_field_test_vectors() -> Vec<BytesToFieldTestCase> {
// Test 2: All 0xFF
let ff_input = [0xFFu8; 96];
let ff_expected =
qp_poseidon_core::serialization::injective_bytes_to_felts::<P2Goldilocks>(&ff_input)
.into_iter()
.map(|g| GoldilocksField::from_noncanonical_u64(g.to_u64()))
.collect();
let ff_expected = p3_to_p2(qp_poseidon_core::serialization::bytes_to_felts(&ff_input));
vectors.push(BytesToFieldTestCase {
input_bytes: ff_input,
expected_felts: ff_expected,
@@ -917,11 +917,7 @@ fn generate_bytes_to_field_test_vectors() -> Vec<BytesToFieldTestCase> {
for (i, byte) in seq_input.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
let seq_expected =
qp_poseidon_core::serialization::injective_bytes_to_felts::<P2Goldilocks>(&seq_input)
.into_iter()
.map(|g| GoldilocksField::from_noncanonical_u64(g.to_u64()))
.collect();
let seq_expected = p3_to_p2(qp_poseidon_core::serialization::bytes_to_felts(&seq_input));
vectors.push(BytesToFieldTestCase {
input_bytes: seq_input,
expected_felts: seq_expected,
@@ -931,11 +927,7 @@ fn generate_bytes_to_field_test_vectors() -> Vec<BytesToFieldTestCase> {
for _ in 0..10 {
let mut input = [0u8; 96];
rng.fill(&mut input[..]);
let expected =
qp_poseidon_core::serialization::injective_bytes_to_felts::<P2Goldilocks>(&input)
.into_iter()
.map(|g| GoldilocksField::from_noncanonical_u64(g.to_u64()))
.collect();
let expected = p3_to_p2(qp_poseidon_core::serialization::bytes_to_felts(&input));
vectors.push(BytesToFieldTestCase {
input_bytes: input,
expected_felts: expected,
@@ -1040,20 +1032,18 @@ fn print_test_failure_hex(
}
}
fn generate_double_hash_test_vectors() -> Vec<DoubleHashTestCase> {
fn generate_hash_squeeze_twice_test_vectors() -> Vec<DoubleHashTestCase> {
let mut vectors = Vec::new();
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0xDEADBEEF);
// Test 1-250: Random header and nonce combinations (25 tests)
// Test 1-250: Random header and nonce combinations
for i in 0..250 {
let mut input = [0u8; 96];
rng.fill(&mut input[..]);
let expected = {
let first_hash = qp_poseidon_core::hash_squeeze_twice(&input);
qp_poseidon_core::hash_squeeze_twice(&first_hash)
};
// Single hash with double squeeze (no longer double hash)
let expected = qp_poseidon_core::hash_squeeze_twice(&input);
vectors.push(DoubleHashTestCase {
input_96_bytes: input,
@@ -4344,11 +4334,11 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {{
results.print_summary()
}
pub async fn test_double_hash(
pub async fn test_hash_squeeze_twice(
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> Result<(), Box<dyn std::error::Error>> {
let test_vectors = generate_double_hash_test_vectors();
let test_vectors = generate_hash_squeeze_twice_test_vectors();
let total_tests = test_vectors.len();
let mut passed_tests = 0;
let mut failed_tests = Vec::new();
@@ -4372,8 +4362,8 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {{
input_array[i] = input_data[input_offset + i];
}}
// Hash the input using double hash (96 bytes -> 64 bytes)
let hash_result = double_hash(input_array);
// Hash the input using single hash with double squeeze (96 bytes -> 64 bytes)
let hash_result = hash_squeeze_twice(input_array);
// Write full 64 bytes (16 u32s) of result as the hash
let output_offset = test_id * 16u;
@@ -4386,12 +4376,12 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {{
);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Double Hash Test Shader"),
label: Some("Hash Squeeze Twice Test Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Double Hash Test Pipeline"),
label: Some("Hash Squeeze Twice Test Pipeline"),
layout: None,
module: &shader,
entry_point: Some("main"),
+56 -52
View File
@@ -55,6 +55,8 @@ pub struct WorkerResult {
pub thread_id: usize,
/// The type of engine (CPU or GPU) that produced this result.
pub engine_type: EngineType,
/// The job ID this result was computed for (used to detect stale results).
pub job_id: u64,
/// The winning candidate, if found.
pub candidate: Option<MiningCandidate>,
/// Number of hashes computed by this worker.
@@ -87,8 +89,8 @@ fn generate_random_nonce() -> U512 {
pub struct MiningJob {
/// Job context with header hash and difficulty
pub ctx: pow_core::JobContext,
/// Epoch number to detect stale results after job transitions
pub epoch: u64,
/// Job ID to detect stale results after job transitions
pub job_id: u64,
}
/// Persistent worker thread pool that keeps threads alive between jobs.
@@ -102,8 +104,8 @@ pub struct WorkerPool {
result_rx: Receiver<WorkerResult>,
/// Shared cancellation flag for all workers
cancel_flag: Arc<AtomicBool>,
/// Job epoch counter - incremented on each new job to detect stale results
job_epoch: Arc<AtomicU64>,
/// Job ID counter - incremented on each new job to detect stale results
current_job_id: Arc<AtomicU64>,
/// Thread handles (for cleanup)
_handles: Vec<thread::JoinHandle<()>>,
/// Number of CPU workers
@@ -123,7 +125,7 @@ impl WorkerPool {
let total_workers = cpu_workers + gpu_devices;
let (result_tx, result_rx) = bounded(total_workers * 64);
let cancel_flag = Arc::new(AtomicBool::new(false));
let job_epoch = Arc::new(AtomicU64::new(0));
let current_job_id = Arc::new(AtomicU64::new(0));
let mut job_senders = Vec::with_capacity(total_workers);
let mut handles = Vec::with_capacity(total_workers);
@@ -145,11 +147,19 @@ impl WorkerPool {
let eng = engine.clone();
let tx = result_tx.clone();
let cancel = cancel_flag.clone();
let epoch = job_epoch.clone();
let job_id_counter = current_job_id.clone();
let tid = thread_id;
let handle = thread::spawn(move || {
worker_loop(tid, EngineType::Cpu, eng, job_rx, tx, cancel, epoch);
worker_loop(
tid,
EngineType::Cpu,
eng,
job_rx,
tx,
cancel,
job_id_counter,
);
});
handles.push(handle);
thread_id += 1;
@@ -167,11 +177,19 @@ impl WorkerPool {
let eng = engine.clone();
let tx = result_tx.clone();
let cancel = cancel_flag.clone();
let epoch = job_epoch.clone();
let job_id_counter = current_job_id.clone();
let tid = thread_id;
let handle = thread::spawn(move || {
worker_loop(tid, EngineType::Gpu, eng, job_rx, tx, cancel, epoch);
worker_loop(
tid,
EngineType::Gpu,
eng,
job_rx,
tx,
cancel,
job_id_counter,
);
});
handles.push(handle);
thread_id += 1;
@@ -183,7 +201,7 @@ impl WorkerPool {
job_senders,
result_rx,
cancel_flag,
job_epoch,
current_job_id,
_handles: handles,
cpu_worker_count: cpu_workers,
gpu_worker_count: gpu_devices,
@@ -191,10 +209,12 @@ impl WorkerPool {
}
/// Start a new mining job. Cancels any currently running job first.
pub fn start_job(&self, header_hash: [u8; 32], difficulty: U512) {
// Increment epoch FIRST - this ensures any in-flight results from the old job
// will be detected as stale when workers check the epoch before sending results
let new_epoch = self.job_epoch.fetch_add(1, Ordering::SeqCst) + 1;
///
/// Returns the new job ID, which can be used to filter stale results.
pub fn start_job(&self, header_hash: [u8; 32], difficulty: U512) -> u64 {
// Increment job ID FIRST - this ensures any in-flight results from the old job
// will be detected as stale when workers check the job ID before sending results
let new_job_id = self.current_job_id.fetch_add(1, Ordering::SeqCst) + 1;
// Cancel any running job
self.cancel_flag.store(true, Ordering::SeqCst);
@@ -209,7 +229,7 @@ impl WorkerPool {
let ctx = pow_core::JobContext::new(header_hash, difficulty);
let job = MiningJob {
ctx,
epoch: new_epoch,
job_id: new_job_id,
};
// Dispatch job to all workers
@@ -220,10 +240,12 @@ impl WorkerPool {
}
log::debug!(
"Job dispatched to {} workers (epoch {})",
"Job dispatched to {} workers (job_id {})",
self.job_senders.len(),
new_epoch
new_job_id
);
new_job_id
}
/// Cancel the current job.
@@ -265,14 +287,14 @@ fn worker_loop(
job_rx: Receiver<MiningJob>,
result_tx: Sender<WorkerResult>,
cancel_flag: Arc<AtomicBool>,
current_epoch: Arc<AtomicU64>,
current_job_id: Arc<AtomicU64>,
) {
let type_str = match engine_type {
EngineType::Cpu => "CPU",
EngineType::Gpu => "GPU",
};
log::info!("{} worker {} started (persistent)", type_str, thread_id);
log::info!("{type_str} worker {thread_id} started (persistent)");
// Main job processing loop
loop {
@@ -281,17 +303,17 @@ fn worker_loop(
Ok(job) => job,
Err(_) => {
// Channel closed, pool is shutting down
log::debug!("{} worker {} shutting down", type_str, thread_id);
log::debug!("{type_str} worker {thread_id} shutting down");
break;
}
};
// Capture the job's epoch for later validation
let job_epoch = job.epoch;
// Capture the job's ID for later validation
let job_id = job.job_id;
// Check if already cancelled before starting
if cancel_flag.load(Ordering::Relaxed) {
log::debug!("{} worker {} skipping cancelled job", type_str, thread_id);
log::debug!("{type_str} worker {thread_id} skipping cancelled job");
continue;
}
@@ -300,10 +322,7 @@ fn worker_loop(
let end = U512::MAX;
log::debug!(
"{} worker {} processing job (epoch {}): range {} to {}",
type_str,
thread_id,
job_epoch,
"{type_str} worker {thread_id} processing job {job_id}: range {} to {}",
format_u512(start),
format_u512(end)
);
@@ -312,15 +331,11 @@ fn worker_loop(
let range = EngineRange { start, end };
let result = engine.search_range(&job.ctx, range, &cancel_flag);
// Check if epoch changed during search - if so, this result is stale
let actual_epoch = current_epoch.load(Ordering::SeqCst);
if actual_epoch != job_epoch {
// Check if job ID changed during search - if so, this result is stale
let actual_job_id = current_job_id.load(Ordering::SeqCst);
if actual_job_id != job_id {
log::debug!(
"⏰ {} worker {} discarding stale result (job epoch {} != current epoch {})",
type_str,
thread_id,
job_epoch,
actual_epoch
"⏰ {type_str} worker {thread_id} discarding stale result (job {job_id} != current {actual_job_id})"
);
// Still send hash count for metrics, but without the candidate
let hash_count = match result {
@@ -332,6 +347,7 @@ fn worker_loop(
let _ = result_tx.try_send(WorkerResult {
thread_id,
engine_type,
job_id,
candidate: None, // Discard the stale candidate
hash_count,
completed: true,
@@ -347,31 +363,18 @@ fn worker_loop(
..
} => {
log::info!(
"🎉 {} worker {} found solution! Nonce: {}, Hash: {} (epoch {})",
type_str,
thread_id,
"🎉 {type_str} worker {thread_id} found solution! Nonce: {}, Hash: {} (job {job_id})",
format_u512(nonce),
format_u512(hash),
job_epoch
);
(Some(MiningCandidate { nonce, work, hash }), hash_count)
}
engine_cpu::EngineStatus::Exhausted { hash_count } => {
log::debug!(
"{} worker {} exhausted range ({} hashes)",
type_str,
thread_id,
hash_count
);
log::debug!("{type_str} worker {thread_id} exhausted range ({hash_count} hashes)");
(None, hash_count)
}
engine_cpu::EngineStatus::Cancelled { hash_count } => {
log::debug!(
"{} worker {} cancelled ({} hashes)",
type_str,
thread_id,
hash_count
);
log::debug!("{type_str} worker {thread_id} cancelled ({hash_count} hashes)");
(None, hash_count)
}
engine_cpu::EngineStatus::Running { .. } => {
@@ -384,6 +387,7 @@ fn worker_loop(
let _ = result_tx.try_send(WorkerResult {
thread_id,
engine_type,
job_id,
candidate,
hash_count,
completed: true,
@@ -395,7 +399,7 @@ fn worker_loop(
engine_gpu::GpuEngine::clear_worker_resources();
}
log::debug!("{} worker {} exited", type_str, thread_id);
log::debug!("{type_str} worker {thread_id} exited");
}
/// Resolve GPU configuration and initialize the engine.
+26 -11
View File
@@ -132,7 +132,10 @@ async fn handle_connection(
metrics::reset_hash_tracker();
// Current job state
let mut current_job_id: Option<String> = None;
// - node_job_id: The string ID from the node (e.g., "27") - used in protocol messages
// - internal_job_id: Our internal numeric ID from WorkerPool - used to detect stale results
let mut node_job_id: Option<String> = None;
let mut internal_job_id: u64 = 0;
let mut job_start_time: Option<Instant> = None;
let mut cpu_hashes: u64 = 0;
let mut gpu_hashes: u64 = 0;
@@ -142,7 +145,7 @@ async fn handle_connection(
loop {
// Poll for worker results (non-blocking via spawn_blocking)
let poll_result = if current_job_id.is_some() && !result_sent_for_current_job {
let poll_result = if node_job_id.is_some() && !result_sent_for_current_job {
let rx = worker_pool.result_receiver().clone();
tokio::task::spawn_blocking(move || rx.recv_timeout(Duration::from_millis(10)))
.await
@@ -153,7 +156,7 @@ async fn handle_connection(
// Handle worker result if any
if let Ok(worker_result) = poll_result {
// Track hashes by engine type
// Track hashes by engine type (always, even for stale results)
match worker_result.engine_type {
EngineType::Cpu => {
cpu_hashes += worker_result.hash_count;
@@ -165,20 +168,28 @@ async fn handle_connection(
}
}
// Only send result for the FIRST solution found
// Check if this result is for the current job (not stale)
if worker_result.job_id != internal_job_id {
log::debug!(
"⏰ Discarding stale result from worker {} (result job_id {} != current {})",
worker_result.thread_id,
worker_result.job_id,
internal_job_id
);
continue;
}
// Only send result for the FIRST solution found for THIS job
if let Some(candidate) = worker_result.candidate {
if !result_sent_for_current_job {
if let Some(ref job_id) = current_job_id {
if let Some(ref job_id) = node_job_id {
let total_hashes = cpu_hashes + gpu_hashes;
let elapsed = job_start_time
.map(|t| t.elapsed().as_secs_f64())
.unwrap_or(0.0);
log::info!(
"⛏️ Job {} completed: {} hashes in {:.2}s ({})",
job_id,
total_hashes,
elapsed,
"⛏️ Job {job_id} completed: {total_hashes} hashes in {elapsed:.2}s ({})",
format_hashrate(total_hashes as f64 / elapsed.max(0.001))
);
@@ -194,6 +205,7 @@ async fn handle_connection(
work: Some(hex::encode(candidate.work)),
hash_count: total_hashes,
elapsed_time: elapsed,
miner_id: None,
};
let msg = MinerMessage::JobResult(result);
@@ -232,6 +244,7 @@ async fn handle_connection(
work: None,
hash_count: 0,
elapsed_time: 0.0,
miner_id: None,
};
let msg = MinerMessage::JobResult(result);
send_message_checked(&connection, &mut send, &msg).await?;
@@ -251,6 +264,7 @@ async fn handle_connection(
work: None,
hash_count: 0,
elapsed_time: 0.0,
miner_id: None,
};
let msg = MinerMessage::JobResult(result);
send_message_checked(&connection, &mut send, &msg).await?;
@@ -262,13 +276,14 @@ async fn handle_connection(
cpu_hashes = 0;
gpu_hashes = 0;
job_start_time = Some(Instant::now());
current_job_id = Some(request.job_id.clone());
node_job_id = Some(request.job_id.clone());
result_sent_for_current_job = false;
log::debug!("Starting job {}", request.job_id);
metrics::set_active_jobs(1);
worker_pool.start_job(header_hash, difficulty);
// start_job returns the internal job ID used to detect stale results
internal_job_id = worker_pool.start_job(header_hash, difficulty);
}
Ok(MinerMessage::JobResult(_)) => {
log::warn!("Received unexpected JobResult from node");
+2 -2
View File
@@ -29,7 +29,7 @@ pub fn format_hashrate(hashes_per_sec: f64) -> String {
}
}
/// Job context for Bitcoin-style PoW mining with double Poseidon2 hashing
/// Job context for PoW mining with Poseidon2 hash_squeeze_twice
#[derive(Debug, Clone)]
pub struct JobContext {
pub header: [u8; 32],
@@ -62,7 +62,7 @@ pub fn step_nonce(nonce: U512) -> U512 {
nonce.saturating_add(U512::from(1u64))
}
/// Compute hash for the current nonce using Bitcoin-style double Poseidon2
/// Compute hash for the current nonce using Poseidon2 hash_squeeze_twice
pub fn hash_from_nonce(ctx: &JobContext, nonce: U512) -> U512 {
let nonce_bytes = nonce.to_big_endian();
qpow_math::get_nonce_hash(ctx.header, nonce_bytes)