Forráskód Böngészése

Add Rust bindings using bindgen.

Signed-off-by: parazyd <parazyd@dyne.org>
parazyd 3 éve
szülő
commit
f149fe76f9
5 módosított fájl, 508 hozzáadás és 0 törlés
  1. 2 0
      .gitignore
  2. 20 0
      Cargo.toml
  3. 79 0
      build.rs
  4. 52 0
      examples/multithreaded.rs
  5. 355 0
      src/lib.rs

+ 2 - 0
.gitignore

@@ -7,3 +7,5 @@ x64/
 Release/
 Debug/
 build/
+Cargo.lock
+target/

+ 20 - 0
Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "randomx"
+version = "1.2.1"
+homepage = "https://github.com/tevador/RandomX/"
+authors = [
+    "Dyne.org Foundation <foundation@dyne.org>",
+    "tevador <tevador@gmail.com>",
+    "The Monero Project",
+]
+license = "BSD-3-Clause"
+edition = "2021"
+
+[target.'cfg(not(target = "x86_64-unknown-linux-musl"))'.build-dependencies]
+bindgen = "0.71.1"
+ 
+[target.'cfg(target = "x86_64-unknown-linux-musl")'.build-dependencies]
+bindgen = {version = "0.71.1", default-features = false, features = ["static"]}
+
+[dependencies]
+bitflags = "2.8.0"

+ 79 - 0
build.rs

@@ -0,0 +1,79 @@
+use std::env;
+use std::io::Write;
+use std::path::PathBuf;
+use std::process::Command;
+
+fn main() {
+    let target = env::var("TARGET").unwrap();
+    let n_threads = std::thread::available_parallelism()
+        .unwrap()
+        .get()
+        .to_string();
+
+    let cargo_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+    let build_dir = &cargo_dir.join("build");
+    std::fs::create_dir_all(build_dir).unwrap();
+    env::set_current_dir(build_dir).unwrap();
+
+    // Generate CMake cache files
+    let b = Command::new("cmake")
+        .arg("-DARCH=native")
+        .arg("..")
+        .output()
+        .expect("Failed to generate Makefile with CMake");
+    std::io::stdout().write_all(&b.stdout).unwrap();
+    std::io::stderr().write_all(&b.stderr).unwrap();
+    assert!(b.status.success());
+
+    // Build the library
+    let b = Command::new("cmake")
+        .arg("--build")
+        .arg(".")
+        .arg("--config")
+        .arg("Release")
+        .arg("-j")
+        .arg(n_threads)
+        .output()
+        .expect("Failed to build RandomX library with CMake");
+    std::io::stdout().write_all(&b.stdout).unwrap();
+    std::io::stderr().write_all(&b.stderr).unwrap();
+    assert!(b.status.success());
+
+    env::set_current_dir(cargo_dir).unwrap();
+
+    // Tell cargo how to find the static library
+    println!(
+        "cargo:rustc-link-search=native={}",
+        build_dir.to_string_lossy()
+    );
+    println!("cargo:rustc-link-lib=static=randomx");
+
+    if target.contains("apple") {
+        println!("cargo:rustc-link-lib=dylib=c++");
+    } else if target.contains("linux") {
+        println!("cargo:rustc-link-lib=dylib=stdc++");
+    } else {
+        unimplemented!()
+    }
+
+    // The bindgen::Builder is the main entry point
+    // to bindgen, and lets you build up options for
+    // the resulting bindings.
+    let bindings = bindgen::Builder::default()
+        // The input header we would like to generate
+        // bindings for.
+        .header("src/randomx.h")
+        // Tell cargo to invalidate the built crate whenever any of the
+        // included header files changed.
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+        // Finish the builder and generate the bindings.
+        .generate()
+        // Unwrap the Result and panic on failure.
+        .expect("Unable to generate bindings");
+
+    // Write the bindings to the $OUT_DIR/bindings.rs file.
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    bindings
+        .write_to_file(out_path.join("bindings.rs"))
+        .expect("Couldn't write bindings!");
+}

+ 52 - 0
examples/multithreaded.rs

@@ -0,0 +1,52 @@
+//! randomx example that calculates many hashes using multiple threads
+
+use randomx::*;
+use std::sync::Arc;
+use std::thread;
+use std::time::Instant;
+use std::vec::Vec;
+
+fn main() {
+    const NUM_THREADS: u32 = 8;
+    // number of hashes to perform in each thread, not the total.
+    const NUM_HASHES: u32 = 5000;
+
+    let start = Instant::now();
+
+    // Try adding `| RandomXFlags::LARGEPAGES`.
+    let flags = RandomXFlags::default() | RandomXFlags::FULLMEM;
+    let dataset = Arc::new(RandomXDataset::new(flags, b"key", NUM_THREADS as usize).unwrap());
+
+    println!("Dataset initialised in {}ms", start.elapsed().as_millis());
+
+    let mut handles = Vec::new();
+
+    let start = Instant::now();
+
+    for i in 0..NUM_THREADS {
+        let dataset = dataset.clone();
+
+        handles.push(thread::spawn(move || {
+            let mut nonce: u32 = i;
+            let vm = RandomXVM::new_fast(flags, &dataset).unwrap();
+
+            for _ in 0..NUM_HASHES {
+                let _ = vm.hash(&nonce.to_be_bytes());
+
+                // e.g. thread 0 will use nonces 0, 8, 16, ...
+                // and thread 1 will use nonces 1, 9, 17, ...
+                nonce += NUM_THREADS;
+            }
+        }));
+    }
+
+    for handle in handles {
+        let _ = handle.join();
+    }
+
+    println!(
+        "Completed {} hashes in {}ms",
+        NUM_THREADS * NUM_HASHES,
+        start.elapsed().as_millis()
+    );
+}

+ 355 - 0
src/lib.rs

@@ -0,0 +1,355 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+//! (Most of the code is taken from https://github.com/moneromint/randomx4r)
+//! Rust bindings to librandomx, a library for computing RandomX hashes.
+//!
+//! # Examples
+//!
+//! ## Light mode hash
+//!
+//! Requires 256M of shared memory.
+//!
+//! ```no_run
+//! use randomx::{RandomXCache, RandomXError, RandomXFlags, RandomXVM};
+//!
+//! // Get flags supported by this system.
+//! let flags = RandomXFlags::default();
+//! let cache = RandomXCache::new(flags, b"key")?;
+//! let vm = RandomXVM::new(flags, &cache)?;
+//! let hash = vm.hash(b"input"); // is a [u8; 32]
+//! # Ok::<(), RandomXError>(())
+//! ```
+//!
+//! ## Fast mode hash
+//!
+//! Requires 2080M of shared memory.
+//!
+//! ```no_run
+//! use randomx::{RandomXDataset, RandomXError, RandomXFlags, RandomXVM};
+//!
+//! // OR the default flags with FULLMEM (aka. fast mode)
+//! let flags = RandomXFlags::default() | RandomXFlags::FULLMEM;
+//! // Speed up dataset initialisation
+//! let threads = std::thread::available_parallelism().unwrap().get();
+//! let dataset = RandomXDataset::new(flags, b"key", threads)?;
+//! let vm = RandomXVM::new_fast(flags, &dataset)?;
+//! let hash = vm.hash(b"input");
+//! # Ok::<(), RandomXError>(())
+//! ```
+//!
+//! # Errors
+//!
+//! Some operations (e.g. allocating a VM or dataset) can fail if the
+//! system doesn't have enough free memory, or if you tried to force a
+//! feature like large pages or AVX2 on a system that does not support
+//! it.
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+use std::marker::PhantomData;
+use std::sync::Arc;
+use std::thread;
+
+use bitflags::bitflags;
+
+#[derive(Debug, Copy, Clone)]
+pub enum RandomXError {
+    /// Occurs when allocating the RandomX cache fails.
+    ///
+    /// Reasons include:
+    /// * Memory allocation fails
+    /// * The JIT flag is set but the current platform does not support it
+    /// * An invalid or unsupported ARGON2 value is set
+    CacheAllocError,
+
+    /// Occurs when allocating a RandomX dataset fails.
+    ///
+    /// Reasons include:
+    /// * Memory allocation fails
+    DatasetAllocError,
+
+    /// Occurs when creating a VM fails.
+    ///
+    /// Reasons included:
+    /// * Scratchpad memory allocation fails
+    /// * Unsupported flags
+    VmAllocError,
+}
+
+impl std::error::Error for RandomXError {}
+impl std::fmt::Display for RandomXError {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self {
+            Self::CacheAllocError => write!(f, "Failed to allocate RandomX cache"),
+            Self::DatasetAllocError => write!(f, "Failed to allocate RandomX dataset"),
+            Self::VmAllocError => write!(f, "Failed to allocate RandomX VM"),
+        }
+    }
+}
+
+bitflags! {
+    /// Represents options that can be used when allocating the
+    /// RandomX dataset or VM.
+    #[derive(Copy, Clone)]
+    pub struct RandomXFlags: u32 {
+        /// Use defaults.
+        const DEFAULT = randomx_flags_RANDOMX_FLAG_DEFAULT;
+
+        /// Allocate memory in large pages.
+        const LARGEPAGES = randomx_flags_RANDOMX_FLAG_LARGE_PAGES;
+
+        /// The RandomX VM will use hardware accelerated AES.
+        const HARDAES = randomx_flags_RANDOMX_FLAG_HARD_AES;
+
+        /// The RandomX VM will use the full dataset.
+        const FULLMEM = randomx_flags_RANDOMX_FLAG_FULL_MEM;
+
+        /// The RandomX VM will use a JIT compiler.
+        const JIT = randomx_flags_RANDOMX_FLAG_JIT;
+
+        /// Make sure that JIT pages are never writable and executable
+        /// at the same time.
+        const SECURE = randomx_flags_RANDOMX_FLAG_SECURE;
+
+        /// Use the SSSE3 extension to speed up Argon2 operations.
+        const ARGON2_SSSE3 = randomx_flags_RANDOMX_FLAG_ARGON2_SSSE3;
+
+        /// Use the AVX2 extension to speed up Argon2 operations.
+        const ARGON2_AVX2 = randomx_flags_RANDOMX_FLAG_ARGON2_AVX2;
+
+        /// Do not use SSSE3 or AVX2 extensions.
+        const ARGON2 = randomx_flags_RANDOMX_FLAG_ARGON2;
+    }
+}
+
+impl Default for RandomXFlags {
+    /// Get the recommended flags to use on the current machine.
+    ///
+    /// Does not include any of the following flags:
+    /// * LARGEPAGES
+    /// * JIT
+    /// * SECURE
+    fn default() -> Self {
+        // Explode if bits do not match up
+        unsafe { Self::from_bits(randomx_get_flags()).unwrap() }
+    }
+}
+
+/// Dataset cache for light-mode hashing.
+pub struct RandomXCache {
+    pub(crate) cache: *mut randomx_cache,
+}
+
+impl RandomXCache {
+    pub fn new(flags: RandomXFlags, key: &[u8]) -> Result<Self, RandomXError> {
+        let cache = unsafe { randomx_alloc_cache(flags.bits()) };
+
+        if cache.is_null() {
+            return Err(RandomXError::CacheAllocError);
+        }
+
+        unsafe {
+            randomx_init_cache(cache, key.as_ptr() as *const std::ffi::c_void, key.len());
+        }
+
+        Ok(RandomXCache { cache })
+    }
+}
+
+impl Drop for RandomXCache {
+    fn drop(&mut self) {
+        unsafe { randomx_release_cache(self.cache) }
+    }
+}
+
+unsafe impl Send for RandomXCache {}
+unsafe impl Sync for RandomXCache {}
+
+pub struct RandomXDataset {
+    pub(crate) dataset: *mut randomx_dataset,
+}
+
+impl RandomXDataset {
+    pub fn new(flags: RandomXFlags, key: &[u8], n_threads: usize) -> Result<Self, RandomXError> {
+        assert!(n_threads > 0);
+
+        let cache = RandomXCache::new(flags, key)?;
+        let dataset = unsafe { randomx_alloc_dataset(flags.bits()) };
+
+        if dataset.is_null() {
+            return Err(RandomXError::DatasetAllocError);
+        }
+
+        let mut dataset = RandomXDataset { dataset };
+
+        let count = unsafe { randomx_dataset_item_count() };
+
+        if n_threads == 1 {
+            unsafe {
+                randomx_init_dataset(dataset.dataset, cache.cache, 0, count);
+            }
+        } else {
+            let mut handles = Vec::new();
+            let cache_arc = Arc::new(cache);
+            let dataset_arc = Arc::new(dataset);
+
+            let size = count / n_threads as u64;
+            let last = count % n_threads as u64;
+            let mut start = 0;
+
+            for i in 0..n_threads {
+                let cache = cache_arc.clone();
+                let dataset = dataset_arc.clone();
+                let mut this_size = size;
+                if i == n_threads - 1 {
+                    this_size += last;
+                }
+                let this_start = start;
+
+                handles.push(thread::spawn(move || unsafe {
+                    randomx_init_dataset(dataset.dataset, cache.cache, this_start, this_size);
+                }));
+
+                start += this_size;
+            }
+
+            for handle in handles {
+                let _ = handle.join();
+            }
+
+            dataset = match Arc::try_unwrap(dataset_arc) {
+                Ok(dataset) => dataset,
+                Err(_) => return Err(RandomXError::DatasetAllocError),
+            };
+        }
+
+        Ok(dataset)
+    }
+}
+
+impl Drop for RandomXDataset {
+    fn drop(&mut self) {
+        unsafe { randomx_release_dataset(self.dataset) }
+    }
+}
+
+unsafe impl Send for RandomXDataset {}
+unsafe impl Sync for RandomXDataset {}
+
+pub struct RandomXVM<'a, T: 'a> {
+    vm: *mut randomx_vm,
+    phantom: PhantomData<&'a T>,
+}
+
+impl RandomXVM<'_, RandomXCache> {
+    pub fn new(flags: RandomXFlags, cache: &'_ RandomXCache) -> Result<Self, RandomXError> {
+        if flags.contains(RandomXFlags::FULLMEM) {
+            return Err(RandomXError::VmAllocError);
+        }
+
+        let vm = unsafe { randomx_create_vm(flags.bits(), cache.cache, std::ptr::null_mut()) };
+
+        if vm.is_null() {
+            return Err(RandomXError::VmAllocError);
+        }
+
+        Ok(Self {
+            vm,
+            phantom: PhantomData,
+        })
+    }
+}
+
+impl RandomXVM<'_, RandomXDataset> {
+    pub fn new_fast(
+        flags: RandomXFlags,
+        dataset: &'_ RandomXDataset,
+    ) -> Result<Self, RandomXError> {
+        if !flags.contains(RandomXFlags::FULLMEM) {
+            return Err(RandomXError::VmAllocError);
+        }
+
+        let vm = unsafe { randomx_create_vm(flags.bits(), std::ptr::null_mut(), dataset.dataset) };
+
+        if vm.is_null() {
+            return Err(RandomXError::VmAllocError);
+        }
+
+        Ok(Self {
+            vm,
+            phantom: PhantomData,
+        })
+    }
+}
+
+impl<T> RandomXVM<'_, T> {
+    /// Calculate the RandomX hash of some data.
+    ///
+    /// ```no_run
+    /// # // ^ no_run, this is already tested in the actual tests
+    /// use randomx::*;
+    /// let flags = RandomXFlags::default();
+    /// let cache = RandomXCache::new(flags, "key".as_bytes())?;
+    /// let vm = RandomXVM::new(flags, &cache)?;
+    /// let hash = vm.hash("input".as_bytes());
+    /// # Ok::<(), RandomXError>(())
+    /// ```
+    pub fn hash(&self, input: &[u8]) -> [u8; RANDOMX_HASH_SIZE as usize] {
+        let mut hash = std::mem::MaybeUninit::<[u8; RANDOMX_HASH_SIZE as usize]>::uninit();
+
+        unsafe {
+            randomx_calculate_hash(
+                self.vm,
+                input.as_ptr() as *const std::ffi::c_void,
+                input.len(),
+                hash.as_mut_ptr() as *mut std::ffi::c_void,
+            );
+
+            hash.assume_init()
+        }
+    }
+}
+
+impl<T> Drop for RandomXVM<'_, T> {
+    fn drop(&mut self) {
+        unsafe { randomx_destroy_vm(self.vm) }
+    }
+}
+
+unsafe impl<T> Send for RandomXVM<'_, T> {}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn can_calc_hash() {
+        let flags = RandomXFlags::default();
+        let cache = RandomXCache::new(flags, "RandomX example key\0".as_bytes()).unwrap();
+        let vm = RandomXVM::new(flags, &cache).unwrap();
+        let hash = vm.hash("RandomX example input\0".as_bytes());
+        let expected = [
+            138, 72, 229, 249, 219, 69, 171, 121, 217, 8, 5, 116, 196, 216, 25, 84, 254, 106, 198,
+            56, 66, 33, 74, 255, 115, 194, 68, 178, 99, 48, 183, 201,
+        ];
+
+        assert_eq!(expected, hash);
+    }
+
+    #[test]
+    fn can_calc_hash_fast() {
+        let flags = RandomXFlags::default() | RandomXFlags::FULLMEM;
+        let n = thread::available_parallelism().unwrap().get();
+        let dataset = RandomXDataset::new(flags, "RandomX example key\0".as_bytes(), n).unwrap();
+        let vm = RandomXVM::new_fast(flags, &dataset).unwrap();
+        let hash = vm.hash("RandomX example input\0".as_bytes());
+        let expected = [
+            138, 72, 229, 249, 219, 69, 171, 121, 217, 8, 5, 116, 196, 216, 25, 84, 254, 106, 198,
+            56, 66, 33, 74, 255, 115, 194, 68, 178, 99, 48, 183, 201,
+        ];
+
+        assert_eq!(expected, hash);
+    }
+}