From 8b72e5cc68cfcf026e9f87e87bd5785c8ad51178 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 09:47:50 +1000 Subject: [PATCH 01/34] Verify compressed solutions in tests --- components/equihash/src/tromp.rs | 113 ++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 9 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 88181ce460..782d8b5ee3 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -98,25 +98,109 @@ pub fn solve_200_9( } } +/// Performs multiple equihash solver runs with equihash parameters `200, 9`, initialising the hash with +/// the supplied partial `input`. Between each run, generates a new nonce of length `N` using the +/// `next_nonce` function. +/// +/// Returns zero or more compressed solutions. +pub fn solve_200_9_compressed( + input: &[u8], + next_nonce: impl FnMut() -> Option<[u8; N]>, +) -> Vec> { + // https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/pow/tromp/equi.h#L34 + const DIGIT_BITS: usize = 200 / (9 + 1); + let solutions = solve_200_9(input, next_nonce); + + solutions + .iter() + .map(|solution| get_minimal_from_indices(solution, DIGIT_BITS)) + .collect() +} + +// Rough translation of GetMinimalFromIndices() from: +// https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/crypto/equihash.cpp#L130-L145 +fn get_minimal_from_indices(indices: &[u32], digit_bits: usize) -> Vec { + let index_bytes = (u32::BITS / 8) as usize; + let digit_bytes = ((digit_bits + 1) + 7) / 8; + assert!(digit_bytes <= index_bytes); + + let len_indices = indices.len() * index_bytes; + let min_len = (digit_bits + 1) * len_indices / (8 * index_bytes); + let byte_pad = index_bytes - digit_bytes; + + // Rough translation of EhIndexToArray(index, array_pointer) from: + // https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/crypto/equihash.cpp#L123-L128 + // + // Big-endian so that lexicographic array comparison is equivalent to integer comparison. + let array: Vec = indices + .iter() + .flat_map(|index| index.to_be_bytes()) + .collect(); + assert_eq!(array.len(), len_indices); + + compress_array(array, min_len, digit_bits + 1, byte_pad) +} + +// Rough translation of CompressArray() from: +// https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/crypto/equihash.cpp#L39-L76 +fn compress_array(array: Vec, out_len: usize, bit_len: usize, byte_pad: usize) -> Vec { + let mut out = Vec::with_capacity(out_len); + + let index_bytes = (u32::BITS / 8) as usize; + assert!(bit_len >= 8); + assert!(8 * index_bytes >= 7 + bit_len); + + let in_width: usize = (bit_len + 7) / 8 + byte_pad; + assert!(out_len == bit_len * array.len() / (8 * in_width)); + + let bit_len_mask: u32 = (1 << (bit_len as u32)) - 1; + + // The acc_bits least-significant bits of acc_value represent a bit sequence + // in big-endian order. + let mut acc_bits: usize = 0; + let mut acc_value: u32 = 0; + + let mut j: usize = 0; + for _i in 0..out_len { + // When we have fewer than 8 bits left in the accumulator, read the next + // input element. + if acc_bits < 8 { + acc_value <<= bit_len; + for x in byte_pad..in_width { + acc_value = acc_value + | (( + // Apply bit_len_mask across byte boundaries + array[j + x] & (bit_len_mask >> (8 * (in_width - x - 1))) as u8 + ) << (8 * (in_width - x - 1))) as u32; // Big-endian + } + j += in_width; + acc_bits += bit_len; + } + + acc_bits -= 8; + out.push((acc_value >> acc_bits) as u8); + } + + out +} + #[cfg(test)] mod tests { - use super::solve_200_9; + use super::solve_200_9_compressed; #[test] fn run_solver() { let input = b"Equihash is an asymmetric PoW based on the Generalised Birthday problem."; - let mut nonce = [ + let mut nonce: [u8; 32] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; + let mut nonces = 0..=u8::MAX; - let solutions = solve_200_9(input, || { - nonce[0] += 1; - if nonce[0] == 0 { - None - } else { - Some(nonce) - } + let solutions = solve_200_9_compressed(input, || { + nonce[0] = nonces.next()?; + println!("Using nonce[0] of {}", nonce[0]); + Some(nonce) }); if solutions.is_empty() { @@ -125,6 +209,17 @@ mod tests { println!("Found {} solutions:", solutions.len()); for solution in solutions { println!("- {:?}", solution); + crate::is_valid_solution(200, 9, input, &nonce, &solution).unwrap_or_else( + |error| { + panic!( + "unexpected invalid equihash 200, 9 solution:\n\ + error: {error:?}\n\ + input: {input:?}\n\ + nonce: {nonce:?}\n\ + solution: {solution:?}" + ) + }, + ); } } } From 5a379ae17fa99e05bf0f70efff0b657ecfd35229 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 09:48:09 +1000 Subject: [PATCH 02/34] Satisfy some lints --- components/equihash/build.rs | 2 ++ components/equihash/src/blake2b.rs | 3 +++ components/equihash/src/tromp.rs | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/components/equihash/build.rs b/components/equihash/build.rs index 19eba012cd..30159d1854 100644 --- a/components/equihash/build.rs +++ b/components/equihash/build.rs @@ -1,3 +1,5 @@ +//! Build script for the equihash tromp solver in C. + fn main() { cc::Build::new() .include("tromp/") diff --git a/components/equihash/src/blake2b.rs b/components/equihash/src/blake2b.rs index 0eab303fdb..75da59d5ab 100644 --- a/components/equihash/src/blake2b.rs +++ b/components/equihash/src/blake2b.rs @@ -2,6 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php . +// This module uses unsafe code for FFI into blake2b. +#![allow(unsafe_code)] + use blake2b_simd::{State, PERSONALBYTES}; use std::ptr; diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 782d8b5ee3..fc5a2d7840 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -1,3 +1,5 @@ +//! Rust interface to the tromp equihash solver. + use std::marker::{PhantomData, PhantomPinned}; use std::slice; @@ -33,6 +35,15 @@ extern "C" { fn equi_sols(eq: *const CEqui) -> *const *const u32; } +/// Performs a single equihash solver run with equihash parameters `p` and hash state `curr_state`. +/// Returns zero or more solutions. +/// +/// # SAFETY +/// +/// The parameters to this function must match the hard-coded parameters in the C++ code. +/// +/// This function uses unsafe code for FFI into the tromp solver. +#[allow(unsafe_code)] unsafe fn worker(p: verify::Params, curr_state: &State) -> Vec> { // Create solver and initialize it. let eq = equi_new( @@ -74,6 +85,11 @@ unsafe fn worker(p: verify::Params, curr_state: &State) -> Vec> { solutions } +/// Performs multiple equihash solver runs with equihash parameters `200, 9`, initialising the hash with +/// the supplied partial `input`. Between each run, generates a new nonce of length `N` using the +/// `next_nonce` function. +/// +/// Returns zero or more solutions. pub fn solve_200_9( input: &[u8], mut next_nonce: impl FnMut() -> Option<[u8; N]>, @@ -91,6 +107,8 @@ pub fn solve_200_9( let mut curr_state = state.clone(); curr_state.update(&nonce); + // SAFETY: the parameters 200,9 match the hard-coded parameters in the C++ code. + #[allow(unsafe_code)] let solutions = unsafe { worker(p, &curr_state) }; if !solutions.is_empty() { break solutions; @@ -189,6 +207,7 @@ mod tests { use super::solve_200_9_compressed; #[test] + #[allow(clippy::print_stdout)] fn run_solver() { let input = b"Equihash is an asymmetric PoW based on the Generalised Birthday problem."; let mut nonce: [u8; 32] = [ From 37bd2421f2a6d40e1c454dc2bfafb1598f6a2faf Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 10:40:56 +1000 Subject: [PATCH 03/34] Only recompile C code if it has changed --- components/equihash/build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/equihash/build.rs b/components/equihash/build.rs index 30159d1854..86c77774c1 100644 --- a/components/equihash/build.rs +++ b/components/equihash/build.rs @@ -5,4 +5,7 @@ fn main() { .include("tromp/") .file("tromp/equi_miner.c") .compile("equitromp"); + + // Tell Cargo to only rerun this build script if the tromp C files or headers change. + println!("cargo:rerun-if-changed=tromp"); } From a73577c6e2024941dd77f8593526c38f5cb1214c Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 10:41:21 +1000 Subject: [PATCH 04/34] Use a 2-byte nonce range in the test --- components/equihash/src/tromp.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index fc5a2d7840..36b691e444 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -214,11 +214,16 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - let mut nonces = 0..=u8::MAX; + let mut nonces = 0..=u16::MAX; let solutions = solve_200_9_compressed(input, || { - nonce[0] = nonces.next()?; - println!("Using nonce[0] of {}", nonce[0]); + let variable_nonce = nonces.next()?; + println!("Using variable nonce [0..2] of {}", variable_nonce); + + let variable_nonce = variable_nonce.to_be_bytes(); + nonce[0] = variable_nonce[0]; + nonce[1] = variable_nonce[1]; + Some(nonce) }); From f3799761da604026acb0b6c5760f4a304ec27571 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 10:50:28 +1000 Subject: [PATCH 05/34] Print solution candidates for debugging --- components/equihash/tromp/equi_miner.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 80346e72ea..de1dd14cdf 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -319,8 +319,13 @@ typedef struct equi equi; listindices1(eq, WK, t, prf); // assume WK odd qsort(prf, PROOFSIZE, sizeof(u32), &compu32); for (u32 i=1; i proof[%d], actual: %d <= %d\n", + i, i-1, prf[i], prf[i-1] + ); return; + } #ifdef EQUIHASH_TROMP_ATOMIC u32 soli = std::atomic_fetch_add_explicit(&eq->nsols, 1U, std::memory_order_relaxed); #else @@ -637,7 +642,7 @@ nc++, candidate(eq, tree_from_bid(bucketid, s0, s1)); } } } -//printf(" %d candidates ", nc); +printf(" %d candidates\n", nc); } size_t equi_nsols(const equi *eq) { From ca77763d7911a23df00878c0fd11b23f05986ba8 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 10:50:52 +1000 Subject: [PATCH 06/34] Add review comments --- components/equihash/src/tromp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 36b691e444..27c9f8f9e9 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -66,6 +66,8 @@ unsafe fn worker(p: verify::Params, curr_state: &State) -> Vec> { }; equi_clearslots(eq); } + // Review Note: nsols is increased here, but only if the solution passes the strictly ordered check. + // With 256 nonces, we get to around 6/9 digits strictly ordered. equi_digitK(eq, 0); let solutions = { From 18062e491329df119605bff39e84b459a4c387e9 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 10:51:22 +1000 Subject: [PATCH 07/34] Only do 30 runs for time trials --- components/equihash/src/tromp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 27c9f8f9e9..d1d0a5e512 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -216,7 +216,7 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - let mut nonces = 0..=u16::MAX; + let mut nonces = 0..=30u16; let solutions = solve_200_9_compressed(input, || { let variable_nonce = nonces.next()?; From 04251a025411f58c279edf5648bf8e70c17f7e45 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 10:51:38 +1000 Subject: [PATCH 08/34] Move allocation out of the loop --- components/equihash/src/tromp.rs | 50 ++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index d1d0a5e512..ba4a373f69 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -44,15 +44,8 @@ extern "C" { /// /// This function uses unsafe code for FFI into the tromp solver. #[allow(unsafe_code)] -unsafe fn worker(p: verify::Params, curr_state: &State) -> Vec> { - // Create solver and initialize it. - let eq = equi_new( - 1, - blake2b::blake2b_clone, - blake2b::blake2b_free, - blake2b::blake2b_update, - blake2b::blake2b_finalize, - ); +unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec> { + // Review Note: nsols is set to zero in C++ here equi_setstate(eq, curr_state); // Initialization done, start algo driver. @@ -82,8 +75,6 @@ unsafe fn worker(p: verify::Params, curr_state: &State) -> Vec> { .collect::>() }; - equi_free(eq); - solutions } @@ -100,22 +91,51 @@ pub fn solve_200_9( let mut state = verify::initialise_state(p.n, p.k, p.hash_output()); state.update(input); - loop { + // Create solver and initialize it. + // + // # SAFETY + // - the parameters 200,9 match the hard-coded parameters in the C++ code. + // - tromp is compiled without multi-threading support, so each instance can only support 1 thread. + // - the blake2b functions are in the correct order in Rust and C++ initializers. + #[allow(unsafe_code)] + let eq = unsafe { + equi_new( + 1, + blake2b::blake2b_clone, + blake2b::blake2b_free, + blake2b::blake2b_update, + blake2b::blake2b_finalize, + ) + }; + + let solutions = loop { let nonce = match next_nonce() { Some(nonce) => nonce, None => break vec![], }; let mut curr_state = state.clone(); + // Review Note: these hashes are changing when the nonce changes curr_state.update(&nonce); - // SAFETY: the parameters 200,9 match the hard-coded parameters in the C++ code. + // SAFETY: + // - the parameters 200,9 match the hard-coded parameters in the C++ code. + // - the eq instance is initilized above. #[allow(unsafe_code)] - let solutions = unsafe { worker(p, &curr_state) }; + let solutions = unsafe { worker(eq, p, &curr_state) }; if !solutions.is_empty() { break solutions; } - } + }; + + // SAFETY: + // - the eq instance is initilized above, and not used after this point. + #[allow(unsafe_code)] + unsafe { + equi_free(eq) + }; + + solutions } /// Performs multiple equihash solver runs with equihash parameters `200, 9`, initialising the hash with From 2ae77d55a40b3b7a4b0b69e7a9c2b7ff017e0799 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 11:33:58 +1000 Subject: [PATCH 09/34] Add safety comments --- components/equihash/src/tromp.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index ba4a373f69..a7b795cbaa 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -45,12 +45,15 @@ extern "C" { /// This function uses unsafe code for FFI into the tromp solver. #[allow(unsafe_code)] unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec> { + // SAFETY: caller must supply a valid `eq` instance. + // // Review Note: nsols is set to zero in C++ here equi_setstate(eq, curr_state); // Initialization done, start algo driver. equi_digit0(eq, 0); equi_clearslots(eq); + // SAFETY: caller must supply a `p` instance that matches the hard-coded values in the C code. for r in 1..p.k { if (r & 1) != 0 { equi_digitodd(eq, r, 0) From c5f0e2e8ba7c1f50617fc2f06dfcd9f8ebd89ec8 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 11:34:16 +1000 Subject: [PATCH 10/34] Skip already tried nonces --- components/equihash/src/tromp.rs | 2 +- components/equihash/tromp/equi_miner.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index a7b795cbaa..990fe0a9ea 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -239,7 +239,7 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - let mut nonces = 0..=30u16; + let mut nonces = 2400..=u16::MAX; let solutions = solve_200_9_compressed(input, || { let variable_nonce = nonces.next()?; diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index de1dd14cdf..d5be076540 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -324,7 +324,7 @@ typedef struct equi equi; "failed check: wanted: proof[%d] > proof[%d], actual: %d <= %d\n", i, i-1, prf[i], prf[i-1] ); - return; + //return; } #ifdef EQUIHASH_TROMP_ATOMIC u32 soli = std::atomic_fetch_add_explicit(&eq->nsols, 1U, std::memory_order_relaxed); From 5b06acfaac64e29ee4a7fbd0ec7fb2bc9bd23f9b Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 11:35:50 +1000 Subject: [PATCH 11/34] More safety comments --- components/equihash/src/tromp.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 990fe0a9ea..3a0868b0c3 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -69,9 +69,18 @@ unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec Date: Fri, 5 Jan 2024 12:01:17 +1000 Subject: [PATCH 12/34] Fix a memory handling issue in the solution array --- components/equihash/src/tromp.rs | 34 ++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 3a0868b0c3..58180d3420 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -32,7 +32,8 @@ extern "C" { fn equi_digiteven(eq: *mut CEqui, r: u32, id: u32); fn equi_digitK(eq: *mut CEqui, id: u32); fn equi_nsols(eq: *const CEqui) -> usize; - fn equi_sols(eq: *const CEqui) -> *const *const u32; + /// Returns `equi_nsols()` solutions of length `2^K`, in a single memory allocation. + fn equi_sols(eq: *const CEqui) -> *const u32; } /// Performs a single equihash solver run with equihash parameters `p` and hash state `curr_state`. @@ -69,23 +70,44 @@ unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec>(); + + assert_eq!(chunks.remainder().len(), 0); + + solutions + }; + + println!( + "{} solutions as cloned vectors of length {:?}", + solutions.len(), solutions .iter() - .map(|solution| slice::from_raw_parts(*solution, solution_len).to_vec()) + .map(|solution| solution.len()) .collect::>() - }; + ); solutions } From 41f4035d20e2fb65647953458dee40ed77671e7b Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 12:01:41 +1000 Subject: [PATCH 13/34] Avoid a panic by ignoring overflow when shifting --- components/equihash/src/tromp.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 58180d3420..0c70ebd96a 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -241,11 +241,12 @@ fn compress_array(array: Vec, out_len: usize, bit_len: usize, byte_pad: usiz if acc_bits < 8 { acc_value <<= bit_len; for x in byte_pad..in_width { - acc_value = acc_value - | (( - // Apply bit_len_mask across byte boundaries - array[j + x] & (bit_len_mask >> (8 * (in_width - x - 1))) as u8 - ) << (8 * (in_width - x - 1))) as u32; // Big-endian + acc_value |= (( + // Apply bit_len_mask across byte boundaries + array[j + x] & (bit_len_mask >> (8 * (in_width - x - 1))) as u8 + ) + .wrapping_shl(8 * (in_width - x - 1) as u32)) + as u32; // Big-endian } j += in_width; acc_bits += bit_len; From d16a40f488ffae24f9c7dff68f0c319099b890bc Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 12:04:20 +1000 Subject: [PATCH 14/34] Restore the duplicate indexes check --- components/equihash/tromp/equi_miner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index d5be076540..de1dd14cdf 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -324,7 +324,7 @@ typedef struct equi equi; "failed check: wanted: proof[%d] > proof[%d], actual: %d <= %d\n", i, i-1, prf[i], prf[i-1] ); - //return; + return; } #ifdef EQUIHASH_TROMP_ATOMIC u32 soli = std::atomic_fetch_add_explicit(&eq->nsols, 1U, std::memory_order_relaxed); From d8745c9307414f6cceb74aef011f3cf28ae75fe5 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 12:04:41 +1000 Subject: [PATCH 15/34] Disable debug-printing in production code --- components/equihash/src/tromp.rs | 6 +++++- components/equihash/tromp/equi_miner.c | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 0c70ebd96a..7ab0f1c0f2 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -71,7 +71,7 @@ unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec Vec Vec Vec>() ); + */ solutions } diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index de1dd14cdf..014adb5931 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -320,10 +320,12 @@ typedef struct equi equi; qsort(prf, PROOFSIZE, sizeof(u32), &compu32); for (u32 i=1; i proof[%d], actual: %d <= %d\n", i, i-1, prf[i], prf[i-1] ); + */ return; } #ifdef EQUIHASH_TROMP_ATOMIC @@ -642,7 +644,7 @@ nc++, candidate(eq, tree_from_bid(bucketid, s0, s1)); } } } -printf(" %d candidates\n", nc); +//printf(" %d candidates\n", nc); } size_t equi_nsols(const equi *eq) { From 4da29c891a674568f6edf00c94ca20bd71a4f75b Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jan 2024 12:05:55 +1000 Subject: [PATCH 16/34] Just print duplicate indexes --- components/equihash/src/tromp.rs | 1 + components/equihash/tromp/equi_miner.c | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 7ab0f1c0f2..cd797c73d7 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -45,6 +45,7 @@ extern "C" { /// /// This function uses unsafe code for FFI into the tromp solver. #[allow(unsafe_code)] +#[allow(clippy::print_stdout)] unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec> { // SAFETY: caller must supply a valid `eq` instance. // diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 014adb5931..3790d8dc80 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -320,12 +320,10 @@ typedef struct equi equi; qsort(prf, PROOFSIZE, sizeof(u32), &compu32); for (u32 i=1; i proof[%d], actual: %d <= %d\n", + "failed dup indexes check: wanted: proof[%d] > proof[%d], actual: %d <= %d\n", i, i-1, prf[i], prf[i-1] ); - */ return; } #ifdef EQUIHASH_TROMP_ATOMIC From 7e7018ee3c6a231a04c01fede00673389688ead8 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 07:39:47 +1000 Subject: [PATCH 17/34] Fix a C++ to C conversion bug with references --- components/equihash/tromp/equi_miner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 3790d8dc80..b105e394cd 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -281,7 +281,7 @@ typedef struct equi equi; u32 getnslots(equi *eq, const u32 r, const u32 bid) { // SHOULD BE METHOD IN BUCKET STRUCT au32 *nslot = &eq->nslots[r&1][bid]; const u32 n = min(*nslot, NSLOTS); - nslot = 0; + *nslot = 0; return n; } void orderindices(u32 *indices, u32 size) { From 5dfd2ad3b4d38d6a10a3d24ed7d6c6cd4277f086 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 07:40:06 +1000 Subject: [PATCH 18/34] Fix unbraced conditional bugs in unused code --- components/equihash/tromp/equi_miner.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index b105e394cd..31b710ed14 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -670,30 +670,30 @@ void *worker(void *vp) { thread_ctx *tp = (thread_ctx *)vp; equi *eq = tp->eq; - if (tp->id == 0) +// if (tp->id == 0) // printf("Digit 0\n"); barrier(&eq->barry); equi_digit0(eq, tp->id); barrier(&eq->barry); if (tp->id == 0) { - eq->xfull = eq->bfull = eq->hfull = 0; + equi_clearslots(eq); showbsizes(eq, 0); } barrier(&eq->barry); for (u32 r = 1; r < WK; r++) { - if (tp->id == 0) +// if (tp->id == 0) // printf("Digit %d", r); barrier(&eq->barry); r&1 ? equi_digitodd(eq, r, tp->id) : equi_digiteven(eq, r, tp->id); barrier(&eq->barry); if (tp->id == 0) { // printf(" x%d b%d h%d\n", eq->xfull, eq->bfull, eq->hfull); - eq->xfull = eq->bfull = eq->hfull = 0; + equi_clearslots(eq); showbsizes(eq, r); } barrier(&eq->barry); } - if (tp->id == 0) +// if (tp->id == 0) // printf("Digit %d\n", WK); equi_digitK(eq, tp->id); barrier(&eq->barry); From 3053149ebdea53d29b11cda2990849a98b71c78b Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 07:40:58 +1000 Subject: [PATCH 19/34] Add some redundant cleanup code, but comment it out --- components/equihash/tromp/equi_miner.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 31b710ed14..ccea58627c 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -204,6 +204,15 @@ typedef struct htalloc htalloc; void dealloctrees(htalloc *hta) { free(hta->heap0); free(hta->heap1); +/* + // Avoid use-after-free + for (int r=0; rtrees0[r/2] = NULL; + else + hta->trees1[r/2] = NULL; + hta->alloced = 0; +*/ } void *htalloc_alloc(htalloc *hta, const u32 n, const u32 sz) { void *mem = calloc(n, sz); From 6d284e29eda799bd39050cbf8543625e14008360 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 07:41:42 +1000 Subject: [PATCH 20/34] Test 4 bytes of changing nonce --- components/equihash/src/tromp.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index cd797c73d7..e8af12d93e 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -280,11 +280,13 @@ mod tests { let solutions = solve_200_9_compressed(input, || { let variable_nonce = nonces.next()?; - println!("Using variable nonce [0..2] of {}", variable_nonce); + println!("Using variable nonce [0..4] of {}", variable_nonce); let variable_nonce = variable_nonce.to_be_bytes(); nonce[0] = variable_nonce[0]; nonce[1] = variable_nonce[1]; + nonce[2] = variable_nonce[2]; + nonce[3] = variable_nonce[3]; Some(nonce) }); From b9c50951dd0aee2a089ecea29206824430cb75b2 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 07:42:41 +1000 Subject: [PATCH 21/34] Comment out some verbose debug code --- components/equihash/tromp/equi_miner.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index ccea58627c..c0e14ce74a 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -329,10 +329,12 @@ typedef struct equi equi; qsort(prf, PROOFSIZE, sizeof(u32), &compu32); for (u32 i=1; i proof[%d], actual: %d <= %d\n", i, i-1, prf[i], prf[i-1] ); + */ return; } #ifdef EQUIHASH_TROMP_ATOMIC From 3f60a59c7a016a7885008ca17e55154c1cbc9da5 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 07:59:39 +1000 Subject: [PATCH 22/34] Fix a type cast error in compress_array() C++ to Rust conversion --- components/equihash/src/tromp.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index e8af12d93e..22d9f27aa1 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -246,12 +246,11 @@ fn compress_array(array: Vec, out_len: usize, bit_len: usize, byte_pad: usiz if acc_bits < 8 { acc_value <<= bit_len; for x in byte_pad..in_width { - acc_value |= (( + acc_value |= ( // Apply bit_len_mask across byte boundaries - array[j + x] & (bit_len_mask >> (8 * (in_width - x - 1))) as u8 + (array[j + x] & ((bit_len_mask >> (8 * (in_width - x - 1))) as u8)) as u32 ) - .wrapping_shl(8 * (in_width - x - 1) as u32)) - as u32; // Big-endian + .wrapping_shl(8 * (in_width - x - 1) as u32); // Big-endian } j += in_width; acc_bits += bit_len; From 0be63ff970d6026e9e2c9a7c5c059c7393e4a0a0 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 08:09:09 +1000 Subject: [PATCH 23/34] Clean up test code --- Cargo.lock | 1 + components/equihash/Cargo.toml | 3 +++ components/equihash/src/tromp.rs | 26 ++++++++++++++------------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0864c570c..b0ec5a5b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -660,6 +660,7 @@ dependencies = [ "blake2b_simd", "byteorder", "cc", + "hex", ] [[package]] diff --git a/components/equihash/Cargo.toml b/components/equihash/Cargo.toml index 5998e54cd0..a4d3403134 100644 --- a/components/equihash/Cargo.toml +++ b/components/equihash/Cargo.toml @@ -16,5 +16,8 @@ byteorder = "1" [build-dependencies] cc = "1" +[dev-dependencies] +hex = "0.4" + [lib] bench = false diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 22d9f27aa1..b3d9a5af94 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -275,13 +275,14 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - let mut nonces = 2400..=u16::MAX; + let mut nonces = 0..=32_u32; + let nonce_count = nonces.clone().count(); let solutions = solve_200_9_compressed(input, || { let variable_nonce = nonces.next()?; println!("Using variable nonce [0..4] of {}", variable_nonce); - let variable_nonce = variable_nonce.to_be_bytes(); + let variable_nonce = variable_nonce.to_le_bytes(); nonce[0] = variable_nonce[0]; nonce[1] = variable_nonce[1]; nonce[2] = variable_nonce[2]; @@ -291,22 +292,23 @@ mod tests { }); if solutions.is_empty() { - println!("Found no solutions"); + // Expected solution rate is documented at: + // https://github.com/tromp/equihash/blob/master/README.md + panic!("Found no solutions after {nonce_count} runs, expected 1.88 solutions per run",); } else { println!("Found {} solutions:", solutions.len()); - for solution in solutions { - println!("- {:?}", solution); - crate::is_valid_solution(200, 9, input, &nonce, &solution).unwrap_or_else( - |error| { - panic!( - "unexpected invalid equihash 200, 9 solution:\n\ + for (sol_num, solution) in solutions.iter().enumerate() { + println!("Validating solution {sol_num}:-\n{}", hex::encode(solution)); + crate::is_valid_solution(200, 9, input, &nonce, solution).unwrap_or_else(|error| { + panic!( + "unexpected invalid equihash 200, 9 solution:\n\ error: {error:?}\n\ input: {input:?}\n\ nonce: {nonce:?}\n\ solution: {solution:?}" - ) - }, - ); + ) + }); + println!("Solution {sol_num} is valid!\n"); } } } From 30436845adaafd466c939cd807833f90dbf02423 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 8 Jan 2024 08:16:02 +1000 Subject: [PATCH 24/34] Add an optional solver feature --- components/equihash/Cargo.toml | 8 +++++++- components/equihash/build.rs | 2 ++ components/equihash/src/lib.rs | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/components/equihash/Cargo.toml b/components/equihash/Cargo.toml index a4d3403134..e2a9e0b054 100644 --- a/components/equihash/Cargo.toml +++ b/components/equihash/Cargo.toml @@ -9,12 +9,18 @@ license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.56.1" +[features] +default = [] + +## Builds the C++ tromp solver and Rust FFI layer. +solver = ["dep:cc"] + [dependencies] blake2b_simd = "1" byteorder = "1" [build-dependencies] -cc = "1" +cc = { version = "1", optional = true } [dev-dependencies] hex = "0.4" diff --git a/components/equihash/build.rs b/components/equihash/build.rs index 86c77774c1..5b7c721fb3 100644 --- a/components/equihash/build.rs +++ b/components/equihash/build.rs @@ -1,11 +1,13 @@ //! Build script for the equihash tromp solver in C. fn main() { + #[cfg(feature = "solver")] cc::Build::new() .include("tromp/") .file("tromp/equi_miner.c") .compile("equitromp"); // Tell Cargo to only rerun this build script if the tromp C files or headers change. + #[cfg(feature = "solver")] println!("cargo:rerun-if-changed=tromp"); } diff --git a/components/equihash/src/lib.rs b/components/equihash/src/lib.rs index d7d20454d2..dee6a15645 100644 --- a/components/equihash/src/lib.rs +++ b/components/equihash/src/lib.rs @@ -27,5 +27,7 @@ mod test_vectors; pub use verify::{is_valid_solution, Error}; +#[cfg(feature = "solver")] mod blake2b; +#[cfg(feature = "solver")] pub mod tromp; From daaaa6aee028f507caee2aa35da4b5eb4e2df2ed Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 10 Jan 2024 11:54:00 +1000 Subject: [PATCH 25/34] Set C pointers to NULL after freeing them to avoid double-frees --- components/equihash/tromp/equi_miner.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index c0e14ce74a..d0a1e6729d 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -202,17 +202,22 @@ typedef struct htalloc htalloc; hta->trees1[r/2] = (bucket1 *)(hta->heap1 + r/2); } void dealloctrees(htalloc *hta) { + if (hta == NULL) { + return; + } + free(hta->heap0); free(hta->heap1); -/* - // Avoid use-after-free + // Avoid use-after-free and double-free + hta->heap0 = NULL; + hta->heap1 = NULL; + for (int r=0; rtrees0[r/2] = NULL; else hta->trees1[r/2] = NULL; hta->alloced = 0; -*/ } void *htalloc_alloc(htalloc *hta, const u32 n, const u32 sz) { void *mem = calloc(n, sz); @@ -266,10 +271,19 @@ typedef struct equi equi; return eq; } void equi_free(equi *eq) { + if (eq == NULL) { + return; + } + dealloctrees(&eq->hta); free(eq->nslots); free(eq->sols); eq->blake2b_free(eq->blake_ctx); + // Avoid use-after-free and double-free + eq->nslots = NULL; + eq->sols = NULL; + eq->blake_ctx = NULL; + free(eq); } void equi_setstate(equi *eq, const BLAKE2bState *ctx) { @@ -502,6 +516,9 @@ typedef struct equi equi; eq->blake2b_update(state, (uchar *)&leb, sizeof(u32)); eq->blake2b_finalize(state, hash, HASHOUT); eq->blake2b_free(state); + // Avoid use-after-free and double-free + state = NULL; + for (u32 i = 0; i Date: Wed, 10 Jan 2024 12:38:34 +1000 Subject: [PATCH 26/34] Fix a memory leak in equi_setstate() --- components/equihash/src/tromp.rs | 1 + components/equihash/tromp/equi_miner.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index b3d9a5af94..42f0eb09d8 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -94,6 +94,7 @@ unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec>(); diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index d0a1e6729d..14f3dd3215 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -263,11 +263,19 @@ typedef struct equi equi; eq->blake2b_free = blake2b_free; eq->blake2b_update = blake2b_update; eq->blake2b_finalize = blake2b_finalize; + const int err = pthread_barrier_init(&eq->barry, NULL, eq->nthreads); assert(!err); + alloctrees(&eq->hta); eq->nslots = (bsizes *)htalloc_alloc(&eq->hta, 2 * NBUCKETS, sizeof(au32)); eq->sols = (proof *)htalloc_alloc(&eq->hta, MAXSOLS, sizeof(proof)); + + // C malloc() does not guarantee zero-initialized memory (but calloc() does) + eq->blake_ctx = NULL; + eq->nsols = 0; + equi_clearslots(eq); + return eq; } void equi_free(equi *eq) { @@ -276,6 +284,7 @@ typedef struct equi equi; } dealloctrees(&eq->hta); + free(eq->nslots); free(eq->sols); eq->blake2b_free(eq->blake_ctx); @@ -287,6 +296,10 @@ typedef struct equi equi; free(eq); } void equi_setstate(equi *eq, const BLAKE2bState *ctx) { + if (eq->blake_ctx) { + eq->blake2b_free(eq->blake_ctx); + } + eq->blake_ctx = eq->blake2b_clone(ctx); memset(eq->nslots, 0, NBUCKETS * sizeof(au32)); // only nslots[0] needs zeroing eq->nsols = 0; From d2c4765d16f20d86f87431920baa6f1d2ef516c4 Mon Sep 17 00:00:00 2001 From: teor Date: Thu, 11 Jan 2024 09:14:19 +1000 Subject: [PATCH 27/34] Check returned solutions are unique --- components/equihash/src/tromp.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index 42f0eb09d8..fac2d895f3 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -37,7 +37,7 @@ extern "C" { } /// Performs a single equihash solver run with equihash parameters `p` and hash state `curr_state`. -/// Returns zero or more solutions. +/// Returns zero or more unique solutions. /// /// # SAFETY /// @@ -95,12 +95,16 @@ unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec>(); assert_eq!(chunks.remainder().len(), 0); + // Sometimes the solver returns identical solutions. + solutions.sort(); + solutions.dedup(); + solutions }; @@ -122,7 +126,7 @@ unsafe fn worker(eq: *mut CEqui, p: verify::Params, curr_state: &State) -> Vec( input: &[u8], mut next_nonce: impl FnMut() -> Option<[u8; N]>, @@ -182,7 +186,7 @@ pub fn solve_200_9( /// the supplied partial `input`. Between each run, generates a new nonce of length `N` using the /// `next_nonce` function. /// -/// Returns zero or more compressed solutions. +/// Returns zero or more unique compressed solutions. pub fn solve_200_9_compressed( input: &[u8], next_nonce: impl FnMut() -> Option<[u8; N]>, @@ -191,10 +195,16 @@ pub fn solve_200_9_compressed( const DIGIT_BITS: usize = 200 / (9 + 1); let solutions = solve_200_9(input, next_nonce); - solutions + let mut solutions: Vec> = solutions .iter() .map(|solution| get_minimal_from_indices(solution, DIGIT_BITS)) - .collect() + .collect(); + + // Just in case the solver returns solutions that become the same when compressed. + solutions.sort(); + solutions.dedup(); + + solutions } // Rough translation of GetMinimalFromIndices() from: From 617e3b2398dbbc165dff8d971ed71c88a8ed40ca Mon Sep 17 00:00:00 2001 From: teor Date: Thu, 11 Jan 2024 09:14:30 +1000 Subject: [PATCH 28/34] Forward-declare a C function --- components/equihash/tromp/equi_miner.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 14f3dd3215..26a99b04a4 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -249,6 +249,7 @@ struct equi { pthread_barrier_t barry; }; typedef struct equi equi; + void equi_clearslots(equi *eq); equi *equi_new( const u32 n_threads, blake2b_clone blake2b_clone, From e83137516f74ee297cb118b19d8b8163b25dc85b Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jan 2024 07:57:42 +1000 Subject: [PATCH 29/34] Add a portable endian.h for htole32() on macOS and Windows --- components/equihash/tromp/equi.h | 4 - components/equihash/tromp/equi_miner.c | 4 + components/equihash/tromp/portable_endian.h | 128 ++++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 components/equihash/tromp/portable_endian.h diff --git a/components/equihash/tromp/equi.h b/components/equihash/tromp/equi.h index 0d159df392..88792e638c 100644 --- a/components/equihash/tromp/equi.h +++ b/components/equihash/tromp/equi.h @@ -4,10 +4,6 @@ #ifndef ZCASH_POW_TROMP_EQUI_H #define ZCASH_POW_TROMP_EQUI_H -#ifdef __APPLE__ -#include "osx_barrier.h" -#endif - #include // for type bool #include // for types uint32_t,uint64_t #include // for functions memset diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 26a99b04a4..eee6efa506 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -22,6 +22,10 @@ #define ZCASH_POW_TROMP_EQUI_MINER_H #include "equi.h" + +// Provides htole32() on macOS and Windows +#include "portable_endian.h" + #include #include #include diff --git a/components/equihash/tromp/portable_endian.h b/components/equihash/tromp/portable_endian.h new file mode 100644 index 0000000000..74575fcd18 --- /dev/null +++ b/components/equihash/tromp/portable_endian.h @@ -0,0 +1,128 @@ +// +// endian.h +// +// https://gist.github.com/panzi/6856583 +// +// I, Mathias Panzenböck, place this file hereby into the public domain. Use +// it at your own risk for whatever you like. In case there are +// jurisdictions that don't support putting things in the public domain you +// can also consider it to be "dual licensed" under the BSD, MIT and Apache +// licenses, if you want to. This code is trivial anyway. Consider it an +// example on how to get the endian conversion functions on different +// platforms. + +// Downloaded from https://raw.githubusercontent.com/mikepb/endian.h/master/endian.h +// on 12 January 2024. + +#ifndef PORTABLE_ENDIAN_H__ +#define PORTABLE_ENDIAN_H__ + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) + +# define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) + +# include + +#elif defined(__APPLE__) + +# include + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) + +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) + +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) + +# include + +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + +# include + +# define be16toh(x) betoh16(x) +# define le16toh(x) letoh16(x) + +# define be32toh(x) betoh32(x) +# define le32toh(x) letoh32(x) + +# define be64toh(x) betoh64(x) +# define le64toh(x) letoh64(x) + +#elif defined(__WINDOWS__) + +# include +# include + +# if BYTE_ORDER == LITTLE_ENDIAN + +# define htobe16(x) htons(x) +# define htole16(x) (x) +# define be16toh(x) ntohs(x) +# define le16toh(x) (x) + +# define htobe32(x) htonl(x) +# define htole32(x) (x) +# define be32toh(x) ntohl(x) +# define le32toh(x) (x) + +# define htobe64(x) htonll(x) +# define htole64(x) (x) +# define be64toh(x) ntohll(x) +# define le64toh(x) (x) + +# elif BYTE_ORDER == BIG_ENDIAN + + /* that would be xbox 360 */ +# define htobe16(x) (x) +# define htole16(x) __builtin_bswap16(x) +# define be16toh(x) (x) +# define le16toh(x) __builtin_bswap16(x) + +# define htobe32(x) (x) +# define htole32(x) __builtin_bswap32(x) +# define be32toh(x) (x) +# define le32toh(x) __builtin_bswap32(x) + +# define htobe64(x) (x) +# define htole64(x) __builtin_bswap64(x) +# define be64toh(x) (x) +# define le64toh(x) __builtin_bswap64(x) + +# else + +# error byte order not supported + +# endif + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#else + +# error platform not supported + +#endif + +#endif From cc2f2d7cd74eaa71d47759cefadf59944f59863e Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jan 2024 08:05:19 +1000 Subject: [PATCH 30/34] Remove unused thread support to enable Windows compilation --- components/equihash/src/tromp.rs | 2 - components/equihash/tromp/equi_miner.c | 41 +++----------- components/equihash/tromp/osx_barrier.h | 75 ------------------------- 3 files changed, 8 insertions(+), 110 deletions(-) delete mode 100644 components/equihash/tromp/osx_barrier.h diff --git a/components/equihash/src/tromp.rs b/components/equihash/src/tromp.rs index fac2d895f3..a0ac90eef5 100644 --- a/components/equihash/src/tromp.rs +++ b/components/equihash/src/tromp.rs @@ -17,7 +17,6 @@ struct CEqui { extern "C" { #[allow(improper_ctypes)] fn equi_new( - n_threads: u32, blake2b_clone: extern "C" fn(state: *const State) -> *mut State, blake2b_free: extern "C" fn(state: *mut State), blake2b_update: extern "C" fn(state: *mut State, input: *const u8, input_len: usize), @@ -144,7 +143,6 @@ pub fn solve_200_9( #[allow(unsafe_code)] let eq = unsafe { equi_new( - 1, blake2b::blake2b_clone, blake2b::blake2b_free, blake2b::blake2b_update, diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index eee6efa506..51ce0146eb 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -28,7 +28,6 @@ #include #include -#include #include typedef uint16_t u16; @@ -246,16 +245,13 @@ struct equi { bsizes *nslots; // PUT IN BUCKET STRUCT proof *sols; au32 nsols; - u32 nthreads; u32 xfull; u32 hfull; u32 bfull; - pthread_barrier_t barry; }; typedef struct equi equi; void equi_clearslots(equi *eq); equi *equi_new( - const u32 n_threads, blake2b_clone blake2b_clone, blake2b_free blake2b_free, blake2b_update blake2b_update, @@ -263,15 +259,11 @@ typedef struct equi equi; ) { assert(sizeof(hashunit) == 4); equi *eq = malloc(sizeof(equi)); - eq->nthreads = n_threads; eq->blake2b_clone = blake2b_clone; eq->blake2b_free = blake2b_free; eq->blake2b_update = blake2b_update; eq->blake2b_finalize = blake2b_finalize; - const int err = pthread_barrier_init(&eq->barry, NULL, eq->nthreads); - assert(!err); - alloctrees(&eq->hta); eq->nslots = (bsizes *)htalloc_alloc(&eq->hta, 2 * NBUCKETS, sizeof(au32)); eq->sols = (proof *)htalloc_alloc(&eq->hta, MAXSOLS, sizeof(proof)); @@ -412,7 +404,7 @@ typedef struct equi equi; u32 nextbo; }; typedef struct htlayout htlayout; - + htlayout htlayout_new(equi *eq, u32 r) { htlayout htl; htl.hta = eq->hta; @@ -528,7 +520,7 @@ typedef struct equi equi; BLAKE2bState* state; htlayout htl = htlayout_new(eq, 0); const u32 hashbytes = hashsize(0); - for (u32 block = id; block < NBLOCKS; block += eq->nthreads) { + for (u32 block = id; block < NBLOCKS; block++) { state = eq->blake2b_clone(eq->blake_ctx); u32 leb = htole32(block); eq->blake2b_update(state, (uchar *)&leb, sizeof(u32)); @@ -564,11 +556,11 @@ typedef struct equi equi; } } } - + void equi_digitodd(equi *eq, const u32 r, const u32 id) { htlayout htl = htlayout_new(eq, r); collisiondata cd; - for (u32 bucketid=id; bucketid < NBUCKETS; bucketid += eq->nthreads) { + for (u32 bucketid=id; bucketid < NBUCKETS; bucketid++) { collisiondata_clear(&cd); slot0 *buck = htl.hta.trees0[(r-1)/2][bucketid]; // optimize by updating previous buck?! u32 bsize = getnslots(eq, r-1, bucketid); // optimize by putting bucketsize with block?! @@ -616,11 +608,11 @@ typedef struct equi equi; } } } - + void equi_digiteven(equi *eq, const u32 r, const u32 id) { htlayout htl = htlayout_new(eq, r); collisiondata cd; - for (u32 bucketid=id; bucketid < NBUCKETS; bucketid += eq->nthreads) { + for (u32 bucketid=id; bucketid < NBUCKETS; bucketid++) { collisiondata_clear(&cd); slot1 *buck = htl.hta.trees1[(r-1)/2][bucketid]; // OPTIMIZE BY UPDATING PREVIOUS u32 bsize = getnslots(eq, r-1, bucketid); @@ -668,12 +660,12 @@ typedef struct equi equi; } } } - + void equi_digitK(equi *eq, const u32 id) { collisiondata cd; htlayout htl = htlayout_new(eq, WK); u32 nc = 0; - for (u32 bucketid = id; bucketid < NBUCKETS; bucketid += eq->nthreads) { + for (u32 bucketid = id; bucketid < NBUCKETS; bucketid++) { collisiondata_clear(&cd); slot0 *buck = htl.hta.trees0[(WK-1)/2][bucketid]; u32 bsize = getnslots(eq, WK-1, bucketid); @@ -700,50 +692,33 @@ nc++, candidate(eq, tree_from_bid(bucketid, s0, s1)); typedef struct { u32 id; - pthread_t thread; equi *eq; } thread_ctx; -void barrier(pthread_barrier_t *barry) { - const int rc = pthread_barrier_wait(barry); - if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) { -// printf("Could not wait on barrier\n"); - pthread_exit(NULL); - } -} - void *worker(void *vp) { thread_ctx *tp = (thread_ctx *)vp; equi *eq = tp->eq; // if (tp->id == 0) // printf("Digit 0\n"); - barrier(&eq->barry); equi_digit0(eq, tp->id); - barrier(&eq->barry); if (tp->id == 0) { equi_clearslots(eq); showbsizes(eq, 0); } - barrier(&eq->barry); for (u32 r = 1; r < WK; r++) { // if (tp->id == 0) // printf("Digit %d", r); - barrier(&eq->barry); r&1 ? equi_digitodd(eq, r, tp->id) : equi_digiteven(eq, r, tp->id); - barrier(&eq->barry); if (tp->id == 0) { // printf(" x%d b%d h%d\n", eq->xfull, eq->bfull, eq->hfull); equi_clearslots(eq); showbsizes(eq, r); } - barrier(&eq->barry); } // if (tp->id == 0) // printf("Digit %d\n", WK); equi_digitK(eq, tp->id); - barrier(&eq->barry); - pthread_exit(NULL); return 0; } diff --git a/components/equihash/tromp/osx_barrier.h b/components/equihash/tromp/osx_barrier.h deleted file mode 100644 index 659c40bf59..0000000000 --- a/components/equihash/tromp/osx_barrier.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef ZCASH_POW_TROMP_OSX_BARRIER_H -#define ZCASH_POW_TROMP_OSX_BARRIER_H - -#ifdef __APPLE__ - -#ifndef PTHREAD_BARRIER_H_ -#define PTHREAD_BARRIER_H_ - -#include -#include - -typedef int pthread_barrierattr_t; -#define PTHREAD_BARRIER_SERIAL_THREAD 1 - -typedef struct -{ - pthread_mutex_t mutex; - pthread_cond_t cond; - int count; - int tripCount; -} pthread_barrier_t; - - -int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) -{ - if(count == 0) - { - errno = EINVAL; - return -1; - } - if(pthread_mutex_init(&barrier->mutex, 0) < 0) - { - return -1; - } - if(pthread_cond_init(&barrier->cond, 0) < 0) - { - pthread_mutex_destroy(&barrier->mutex); - return -1; - } - barrier->tripCount = count; - barrier->count = 0; - - return 0; -} - -int pthread_barrier_destroy(pthread_barrier_t *barrier) -{ - pthread_cond_destroy(&barrier->cond); - pthread_mutex_destroy(&barrier->mutex); - return 0; -} - -int pthread_barrier_wait(pthread_barrier_t *barrier) -{ - pthread_mutex_lock(&barrier->mutex); - ++(barrier->count); - if(barrier->count >= barrier->tripCount) - { - barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); - pthread_mutex_unlock(&barrier->mutex); - return PTHREAD_BARRIER_SERIAL_THREAD; - } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); - pthread_mutex_unlock(&barrier->mutex); - return 0; - } -} - -#endif // PTHREAD_BARRIER_H_ -#endif // __APPLE__ - -#endif // ZCASH_POW_TROMP_OSX_BARRIER_H From 742ca1b12a82bbf1a2601999db609c43682a3e4e Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jan 2024 08:22:48 +1000 Subject: [PATCH 31/34] Don't import a header that's missing in Windows CI --- components/equihash/tromp/portable_endian.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/equihash/tromp/portable_endian.h b/components/equihash/tromp/portable_endian.h index 74575fcd18..4a71ce7a7a 100644 --- a/components/equihash/tromp/portable_endian.h +++ b/components/equihash/tromp/portable_endian.h @@ -71,7 +71,9 @@ #elif defined(__WINDOWS__) # include -# include + +// Not available in librustzcash CI +//# include # if BYTE_ORDER == LITTLE_ENDIAN From 97a1e0adb57a83acd02e6eea5bb06fbb3b04ec21 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jan 2024 09:06:42 +1000 Subject: [PATCH 32/34] MSVC demands more constant expressions --- components/equihash/tromp/equi.h | 8 ++++---- components/equihash/tromp/equi_miner.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/equihash/tromp/equi.h b/components/equihash/tromp/equi.h index 88792e638c..7b3969f52f 100644 --- a/components/equihash/tromp/equi.h +++ b/components/equihash/tromp/equi.h @@ -28,10 +28,10 @@ typedef unsigned char uchar; #define DIGITBITS (WN/(NDIGITS)) #define PROOFSIZE (1<nslots[r&1][bid]; - const u32 n = min(*nslot, NSLOTS); + const u32 n = minu32(*nslot, NSLOTS); *nslot = 0; return n; } @@ -374,7 +374,7 @@ typedef struct equi equi; u32 binsizes[65]; memset(binsizes, 0, 65 * sizeof(u32)); for (u32 bucketid = 0; bucketid < NBUCKETS; bucketid++) { - u32 bsize = min(eq->nslots[r&1][bucketid], NSLOTS) >> (SLOTBITS-6); + u32 bsize = minu32(eq->nslots[r&1][bucketid], NSLOTS) >> (SLOTBITS-6); binsizes[bsize]++; } for (u32 i=0; i < 65; i++) { From e8794775f02ed83b9433ee73cafcc1c5339d654a Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jan 2024 11:12:25 +1000 Subject: [PATCH 33/34] Clear slots when setting the hash state --- components/equihash/tromp/equi_miner.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index 8f6edc0410..b0ec26ddba 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -299,6 +299,7 @@ typedef struct equi equi; eq->blake_ctx = eq->blake2b_clone(ctx); memset(eq->nslots, 0, NBUCKETS * sizeof(au32)); // only nslots[0] needs zeroing + equi_clearslots(eq); eq->nsols = 0; } void equi_clearslots(equi *eq) { From aff6fac6cb9c7390565313733f0ba7d679166504 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jan 2024 11:24:16 +1000 Subject: [PATCH 34/34] Keep the C worker up to date with Rust bug fixes --- components/equihash/tromp/equi_miner.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/equihash/tromp/equi_miner.c b/components/equihash/tromp/equi_miner.c index b0ec26ddba..43ffe2a91e 100644 --- a/components/equihash/tromp/equi_miner.c +++ b/components/equihash/tromp/equi_miner.c @@ -702,6 +702,9 @@ void *worker(void *vp) { // if (tp->id == 0) // printf("Digit 0\n"); + if (tp->id == 0) { + equi_clearslots(eq); + } equi_digit0(eq, tp->id); if (tp->id == 0) { equi_clearslots(eq);