tests pass and it mines now

This commit is contained in:
illuzen
2025-12-03 17:48:52 +08:00
parent ded099d8bd
commit 44e62ce2f4
3 changed files with 221 additions and 4 deletions
+196
View File
@@ -0,0 +1,196 @@
use pow_core::{JobContext, hash_from_nonce};
use primitive_types::U512;
use wgpu::util::DeviceExt;
use rand::Rng;
use rand::SeedableRng;
pub async fn test_end_to_end_mining(
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Running End-to-End Mining Test...");
// 1. Setup a job context
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(12345);
let mut header = [0u8; 32];
rng.fill(&mut header);
// Use difficulty 1 so target is MAX. Any hash should pass.
let difficulty = U512::from(1u64);
let ctx = JobContext::new(header, difficulty);
// 2. Pick a nonce
let nonce_val = U512::from(123456789u64);
// 3. Compute expected hash using CPU (pow-core)
let expected_hash = hash_from_nonce(&ctx, nonce_val);
println!("CPU Expected Hash: {:x}", expected_hash);
// 4. Run GPU Mining for this specific nonce
// Header Buffer
let mut header_u32s = [0u32; 8];
for i in 0..8 {
let chunk = &ctx.header[i * 4..(i + 1) * 4];
header_u32s[i] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
let header_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Header Buffer"),
contents: bytemuck::cast_slice(&header_u32s),
usage: wgpu::BufferUsages::STORAGE,
});
// Target Buffer
let target_bytes = ctx.target.to_little_endian();
let mut target_u32s = [0u32; 16];
for i in 0..16 {
let chunk = &target_bytes[i * 4..(i + 1) * 4];
target_u32s[i] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
let target_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Target Buffer"),
contents: bytemuck::cast_slice(&target_u32s),
usage: wgpu::BufferUsages::STORAGE,
});
// Start Nonce Buffer
// We set start_nonce = nonce_val. Thread 0 will check start_nonce + 0 = nonce_val.
let start_nonce_bytes = nonce_val.to_little_endian();
let mut start_nonce_u32s = [0u32; 16];
for i in 0..16 {
let chunk = &start_nonce_bytes[i * 4..(i + 1) * 4];
start_nonce_u32s[i] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
let start_nonce_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Start Nonce Buffer"),
contents: bytemuck::cast_slice(&start_nonce_u32s),
usage: wgpu::BufferUsages::STORAGE,
});
// Results Buffer
let results_size = (1 + 16 + 16) * 4;
let results_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Results Buffer"),
size: results_size as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
// Clear results buffer
let zeros = vec![0u8; results_size];
queue.write_buffer(&results_buffer, 0, &zeros);
// Load Shader
let shader_source = include_str!("mining.wgsl");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Mining Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Mining Pipeline"),
layout: None,
module: &shader,
entry_point: Some("mining_main"),
compilation_options: Default::default(),
cache: None,
});
let bind_group_layout = pipeline.get_bind_group_layout(0);
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Mining Bind Group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: results_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: header_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: start_nonce_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: target_buffer.as_entire_binding(),
},
],
});
// Run Compute Pass
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Mining Encoder"),
});
{
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Mining Compute Pass"),
timestamp_writes: None,
});
cpass.set_pipeline(&pipeline);
cpass.set_bind_group(0, &bind_group, &[]);
cpass.dispatch_workgroups(1, 1, 1); // 1 workgroup, 256 threads. Thread 0 will check nonce_val.
}
// Read Results
let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Staging Buffer"),
size: results_size as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
encoder.copy_buffer_to_buffer(&results_buffer, 0, &staging_buffer, 0, results_size as u64);
queue.submit(Some(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let (sender, receiver) = futures::channel::oneshot::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
device.poll(wgpu::PollType::Wait {
submission_index: None,
timeout: None,
}).unwrap();
if let Ok(Ok(())) = receiver.await {
let data = buffer_slice.get_mapped_range();
let result_u32s: &[u32] = bytemuck::cast_slice(&data);
if result_u32s[0] != 0 {
println!("GPU found solution!");
// Parse nonce
let mut nonce_bytes = [0u8; 64];
for i in 0..16 {
let bytes = result_u32s[1 + i].to_le_bytes();
nonce_bytes[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
}
let found_nonce = U512::from_little_endian(&nonce_bytes);
println!("GPU Nonce: {}", found_nonce);
assert_eq!(found_nonce, nonce_val, "Nonce mismatch!");
// Parse hash
let mut hash_bytes = [0u8; 64];
for i in 0..16 {
let bytes = result_u32s[17 + i].to_le_bytes();
hash_bytes[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
}
let found_hash = U512::from_little_endian(&hash_bytes);
println!("GPU Hash: {:x}", found_hash);
assert_eq!(found_hash, expected_hash, "Hash mismatch!");
println!("✅ End-to-End Test Passed!");
} else {
println!("❌ GPU did not find solution (should have passed with MAX target)");
return Err("GPU did not find solution".into());
}
} else {
return Err("Failed to map buffer".into());
}
Ok(())
}
+10
View File
@@ -6,6 +6,7 @@ use qp_plonky2_field::types::{Field, PrimeField64};
use wgpu::{self, util::DeviceExt};
mod tests;
mod end_to_end_tests;
fn u8_array_to_u32_array(bytes: &[u8; 96]) -> [u32; 24] {
let mut result = [0u32; 24];
@@ -121,6 +122,15 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("❌ Double hash tests failed: {}", e);
}
if let Err(e) = end_to_end_tests::test_end_to_end_mining(&device, &queue).await {
eprintln!("❌ End-to-end mining test failed: {}", e);
}
println!("\nAll tests completed!");
if let Err(e) = end_to_end_tests::test_end_to_end_mining(&device, &queue).await {
eprintln!("❌ End-to-end mining test failed: {}", e);
}
println!("\nAll tests completed!");
// generate_correct_wgsl_constants();
+15 -4
View File
@@ -973,18 +973,29 @@ fn mining_main(@builtin(global_invocation_id) global_id: vec3<u32>) {
input[8u + i] = rev;
}
// Hash
let hash = double_hash(input);
// Hash (Big Endian)
let hash_be = double_hash(input);
// Convert to Little Endian for difficulty check and storage
var hash_le: array<u32, 16>;
for (var i = 0u; i < 16u; i++) {
let val = hash_be[15u - i];
// Reverse bytes in u32
hash_le[i] = ((val & 0xFFu) << 24u) |
((val & 0xFF00u) << 8u) |
((val & 0xFF0000u) >> 8u) |
((val & 0xFF000000u) >> 24u);
}
// Check target
if (is_below_target(hash, difficulty_target)) {
if (is_below_target(hash_le, difficulty_target)) {
// Try to claim the solution
if (atomicExchange(&results[0], 1u) == 0u) {
// We won! Write nonce and hash
// results layout: [0]=found, [1..16]=nonce, [17..32]=hash
for (var i = 0u; i < 16u; i++) {
atomicStore(&results[1u + i], current_nonce[i]);
atomicStore(&results[17u + i], hash[i]);
atomicStore(&results[17u + i], hash_le[i]);
}
}
}