tests pass and it mines now
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user