diff --git a/README.md b/README.md
index e914ea5..68039a8 100644
--- a/README.md
+++ b/README.md
@@ -115,7 +115,7 @@ To include `xdagj-native-randomx` in your project, add the following dependency
io.xdag
xdagj-native-randomx
- 0.2.0
+ 0.2.2
```
@@ -138,27 +138,27 @@ public class Example {
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
// Get supported RandomX flags for the current CPU
- Set flags = RandomXUtils.getFlagsSet();
+ Set flags = RandomXUtils.getRecommendedFlags();
System.out.println("Supported flags: " + flags);
- int combinedFlags = RandomXFlag.toValue(flags);
- System.out.println("Combined flags value: " + combinedFlags);
- // Initialize RandomX cache with the supported flags
+ // Initialize RandomX cache (key will be set via template)
RandomXCache cache = new RandomXCache(flags);
- cache.init(keyBytes);
// Create and configure RandomXTemplate using builder pattern
byte[] hash;
try (RandomXTemplate template = RandomXTemplate.builder()
.cache(cache)
- .miningMode(false) // Set to false for normal hashing mode
+ .miningMode(false)
.flags(flags)
.build()) {
- // Initialize the template with the configured settings
+ // Set the key for RandomX operations. This will initialize the cache.
+ template.changeKey(keyBytes);
+
+ // Initialize the template's VM with the configured settings
template.init();
- // Calculate hash of the input key
+ // Calculate hash of the input
hash = template.calculateHash(keyBytes);
}
@@ -184,10 +184,10 @@ public class Example {
### Linux Performance Results
| Benchmark | Mode | Cnt | Score | Error | Units |
|:------------------------------:|:-----:|:---:|:-------:|:------:|:-----:|
-| RandomXBenchmark.lightBatch | thrpt | | 328.736 | | ops/s |
-| RandomXBenchmark.lightNoBatch | thrpt | | 325.383 | | ops/s |
-| RandomXBenchmark.miningBatch | thrpt | | 2777.939 | | ops/s |
-| RandomXBenchmark.miningNoBatch | thrpt | | 2817.811 | | ops/s |
+| RandomXBenchmark.lightBatch | thrpt | | 416.114 | | ops/s |
+| RandomXBenchmark.lightNoBatch | thrpt | | 424.865 | | ops/s |
+| RandomXBenchmark.miningBatch | thrpt | | 1818.991 | | ops/s |
+| RandomXBenchmark.miningNoBatch | thrpt | | 2191.774 | | ops/s |
---
@@ -196,17 +196,15 @@ public class Example {
- **CPU**: Apple M3 Pro
- **RAM**: 36 GB
- **thread**: 8
-- **RandomX Flags**: [DEFAULT, HARD_AES, SECURE]
-
-JIT flag will cause jvm to crash in MacOS
+- **RandomX Flags**: [DEFAULT, JIT, SECURE]
### MacOS Performance Results
-| Benchmark | Mode | Cnt | Score | Error | Units |
-|:------------------------------:|:-----:|:---:|:-------:|:------:|:-----:|
-| RandomXBenchmark.lightBatch | thrpt | | 32.864 | | ops/s |
-| RandomXBenchmark.lightNoBatch | thrpt | | 33.683 | | ops/s |
-| RandomXBenchmark.miningBatch | thrpt | | 554.966 | | ops/s |
-| RandomXBenchmark.miningNoBatch | thrpt | | 570.060 | | ops/s |
+| Benchmark | Mode | Cnt | Score | Error | Units |
+|:------------------------------:|:-----:|:---:|:--------:|:------:|:-----:|
+| RandomXBenchmark.lightBatch | thrpt | | 416.114 | | ops/s |
+| RandomXBenchmark.lightNoBatch | thrpt | | 424.865 | | ops/s |
+| RandomXBenchmark.miningBatch | thrpt | | 1818.991 | | ops/s |
+| RandomXBenchmark.miningNoBatch | thrpt | | 2191.774 | | ops/s |
---
diff --git a/src/main/java/io/xdag/crypto/randomx/Example.java b/src/main/java/io/xdag/crypto/randomx/Example.java
index 32f756a..7dfda1b 100644
--- a/src/main/java/io/xdag/crypto/randomx/Example.java
+++ b/src/main/java/io/xdag/crypto/randomx/Example.java
@@ -40,34 +40,33 @@ public class Example {
* @param args Command line arguments (not used)
*/
public static void main(String[] args) {
- // Key to be hashed
+ // Key (or message) to be hashed
String key = "hello xdagj-native-randomx";
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
+ System.out.println("Input message: " + key);
- // Get supported RandomX flags for the current CPU
- Set flags = RandomXUtils.getFlagsSet();
- System.out.println("Supported flags: " + flags);
- int combinedFlags = RandomXFlag.toValue(flags);
- System.out.println("Combined flags value: " + combinedFlags);
+ // Get recommended RandomX flags for the current CPU
+ Set flags = RandomXUtils.getRecommendedFlags();
- // Initialize RandomX cache with the supported flags
+ // Allocate RandomX cache with the recommended flags
+ // The key will be set and cache initialized via RandomXTemplate
RandomXCache cache = new RandomXCache(flags);
- cache.init(keyBytes);
- // Create and configure RandomXTemplate using builder pattern
+ // Create and configure RandomXTemplate using a builder pattern
byte[] hash;
try (RandomXTemplate template = RandomXTemplate.builder()
- .cache(cache)
- .miningMode(false) // Set to false for normal hashing mode
- .flags(flags)
+ .cache(cache) // Provide the cache instance (not yet initialized with key)
+ .miningMode(false) // Set to false for light hashing mode (no dataset)
+ .flags(flags) // Provide the base flags
.build()) {
+
+ // Set the key for RandomX operations. This will initialize the cache.
+ template.changeKey(keyBytes);
- // Initialize the template with the configured settings
+ // Initialize the template. This creates the VM.
template.init();
-
- // Calculate hash of the input key
hash = template.calculateHash(keyBytes);
- }
+ } // try-with-resources automatically calls template.close()
// Format and display the results
HexFormat hex = HexFormat.of();
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXCache.java b/src/main/java/io/xdag/crypto/randomx/RandomXCache.java
index 211249b..3575f43 100644
--- a/src/main/java/io/xdag/crypto/randomx/RandomXCache.java
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXCache.java
@@ -26,119 +26,108 @@
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import lombok.Getter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
+import java.io.Closeable;
import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
/**
- * A class that encapsulates the RandomX cache functionality.
- * This class manages the allocation, initialization and release of RandomX cache memory.
- * It implements AutoCloseable to ensure proper resource cleanup.
+ * Represents a RandomX Cache object.
+ * This class manages the allocation, initialization, and release of the native RandomX cache structure.
*/
-@Getter
-public class RandomXCache implements AutoCloseable {
- private static final Logger logger = LoggerFactory.getLogger(RandomXCache.class);
-
- /**
- * Pointer to the allocated RandomX cache memory.
- * -- GETTER --
- * Returns the pointer to the allocated cache memory.
- */
- @Getter
+@Slf4j
+public class RandomXCache implements Closeable {
private final Pointer cachePointer;
-
- /**
- * Pointer to the key used for cache initialization
- */
- private volatile Pointer keyPointer;
- private int keyLength;
- private final ReentrantLock lock = new ReentrantLock();
+ @Getter
+ private final Set flags;
/**
- * Constructs a new RandomXCache with the specified flags.
- * Allocates memory for the cache using the native RandomX library.
+ * Allocates a new RandomX cache.
*
- * @param flags Set of RandomXFlag values that configure the cache behavior
- * @throws IllegalArgumentException if flags is null or empty
- * @throws IllegalStateException if cache allocation fails
+ * @param flags Flags used to initialize the cache.
+ * @throws RuntimeException if cache allocation fails.
*/
public RandomXCache(Set flags) {
- if (flags == null || flags.isEmpty()) {
- throw new IllegalArgumentException("Flags cannot be null or empty");
- }
-
+ this.flags = flags;
int combinedFlags = RandomXFlag.toValue(flags);
- logger.debug("Allocating RandomX cache with flags: {}", flags);
-
- this.cachePointer = RandomXJNALoader.getInstance().randomx_alloc_cache(combinedFlags);
+ log.debug("Allocating RandomX cache with flags: {} ({})", flags, combinedFlags);
+ // Use RandomXNative for allocation
+ this.cachePointer = RandomXNative.randomx_alloc_cache(combinedFlags);
if (this.cachePointer == null) {
- throw new IllegalStateException("Failed to allocate RandomX cache");
+ String errorMsg = String.format("Failed to allocate RandomX cache with flags: %s (%d)", flags, combinedFlags);
+ log.error(errorMsg);
+ throw new RuntimeException(errorMsg);
}
-
- logger.debug("RandomX cache allocated successfully");
+ log.info("RandomX cache allocated successfully at pointer: {}", Pointer.nativeValue(this.cachePointer));
}
/**
- * Initializes the cache with the provided key.
- * This method is thread-safe and can be called multiple times with different keys.
+ * Initializes the RandomX cache with the specified key.
*
- * @param key byte array containing the key data
- * @throws IllegalArgumentException if key is null or empty
+ * @param key Key (seed) used to initialize the cache.
+ * @throws RuntimeException if cache initialization fails.
+ * @throws IllegalStateException if the cache is not allocated.
*/
public void init(byte[] key) {
+ if (cachePointer == null) {
+ throw new IllegalStateException("Cache is not allocated.");
+ }
if (key == null || key.length == 0) {
- throw new IllegalArgumentException("Key cannot be null or empty");
+ throw new IllegalArgumentException("Key cannot be null or empty for cache initialization.");
}
- lock.lock();
+ // Use JNA Memory to manage native memory
+ Memory keyPointer = new Memory(key.length);
try {
- long startTime = System.nanoTime();
- logger.debug("Initializing cache with key length: {}", key.length);
-
- // Free old key pointer if exists
- if (keyPointer != null) {
- keyPointer.clear(keyLength);
- }
-
- // Allocate and initialize new key
- keyLength = key.length;
- keyPointer = new Memory(key.length);
keyPointer.write(0, key, 0, key.length);
-
- RandomXJNALoader.getInstance().randomx_init_cache(
- this.cachePointer,
- keyPointer,
- key.length
+ log.debug("Initializing RandomX cache with key of length: {}", key.length);
+ // Use RandomXNative for initialization
+ RandomXNative.randomx_init_cache(
+ this.cachePointer,
+ keyPointer,
+ key.length
);
-
- long endTime = System.nanoTime();
- logger.debug("Cache initialization completed in {} ms", (endTime - startTime) / 1_000_000);
+ log.info("RandomX cache initialized successfully.");
+ } catch (Exception e) {
+ log.error("Failed to initialize RandomX cache", e);
+ // Even if initialization fails, attempt to release memory
+ close(); // Release cachePointer
+ throw new RuntimeException("Failed to initialize RandomX cache", e);
} finally {
- lock.unlock();
+ // Memory objects do not need to be manually released; JNA's GC will handle it,
+ // but nullifying the reference immediately might help GC reclaim it faster.
+ keyPointer = null; // Help GC
}
}
/**
- * Releases the allocated cache memory and key memory.
- * This method is thread-safe and idempotent.
+ * Gets the pointer to the underlying native RandomX cache structure.
+ *
+ * @return Pointer to the native cache.
+ * @throws IllegalStateException if the cache is not allocated.
+ */
+ public Pointer getCachePointer() {
+ if (cachePointer == null) {
+ throw new IllegalStateException("Cache is not allocated.");
+ }
+ return cachePointer;
+ }
+
+ /**
+ * Releases the resources occupied by the native RandomX cache.
+ * This method should be called after finishing with the cache to prevent memory leaks.
*/
@Override
public void close() {
- lock.lock();
- try {
- if (keyPointer != null) {
- keyPointer.clear(keyLength);
- keyPointer = null;
+ if (cachePointer != null) {
+ log.debug("Releasing RandomX cache at pointer: {}", Pointer.nativeValue(cachePointer));
+ try {
+ // Use RandomXNative for release
+ RandomXNative.randomx_release_cache(cachePointer);
+ log.info("RandomX cache released successfully");
+ } catch (Throwable t) {
+ log.error("Error occurred while releasing RandomX cache. Pointer: {}", Pointer.nativeValue(cachePointer), t);
}
-
- if (cachePointer != null) {
- RandomXJNALoader.getInstance().randomx_release_cache(cachePointer);
- logger.debug("RandomX cache released successfully");
- }
- } finally {
- lock.unlock();
}
}
}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXDataset.java b/src/main/java/io/xdag/crypto/randomx/RandomXDataset.java
index 03179d4..97f375e 100644
--- a/src/main/java/io/xdag/crypto/randomx/RandomXDataset.java
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXDataset.java
@@ -36,71 +36,83 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.TimeUnit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
/**
- * A class that encapsulates the RandomX dataset functionality with multi-threaded initialization support.
- * This class manages the allocation, initialization and release of RandomX dataset memory.
- * It implements AutoCloseable to ensure proper resource cleanup.
+ * Encapsulates the RandomX dataset functionality with multi-threaded initialization support.
+ * Manages the allocation, initialization, and release of RandomX datasets, with support for multi-threaded initialization.
+ * This class implements AutoCloseable for resource management.
+ * Implement the AutoCloseable interface for resource management.
*/
+@Slf4j
public class RandomXDataset implements AutoCloseable {
- private static final Logger logger = LoggerFactory.getLogger(RandomXDataset.class);
-
/**
- * Pointer to the allocated RandomX dataset memory
+ * Pointer to the allocated RandomX dataset memory.
*/
- private final Pointer dataset;
+ private final Pointer datasetPointer;
+
+ @Getter
+ private final Set flags; // Store flags used for allocation
/**
- * Constructs a new RandomXDataset with the specified flags.
- * Allocates memory for the dataset using the native RandomX library.
+ * Constructs a new RandomXDataset and allocates memory for it.
*
- * @param flags Set of RandomXFlag values that configure the dataset behavior
- * @throws IllegalStateException if dataset allocation fails
+ * @param flags Set of RandomXFlag values used to configure the dataset behavior.
+ * @throws RuntimeException if dataset allocation fails.
*/
public RandomXDataset(Set flags) {
if (flags == null || flags.isEmpty()) {
- throw new IllegalArgumentException("Flags cannot be null or empty");
+ throw new IllegalArgumentException("Flags cannot be null or empty for dataset allocation.");
}
-
+ this.flags = flags;
int combinedFlags = RandomXFlag.toValue(flags);
- this.dataset = RandomXJNALoader.getInstance().randomx_alloc_dataset(combinedFlags);
-
- if (dataset == null) {
- throw new IllegalStateException("Failed to allocate RandomX dataset");
+ log.debug("Allocating RandomX dataset with flags: {} ({})", flags, combinedFlags);
+
+ // Use RandomXNative for allocation
+ this.datasetPointer = RandomXNative.randomx_alloc_dataset(combinedFlags);
+
+ if (datasetPointer == null) {
+ String errorMsg = String.format("Failed to allocate RandomX dataset with flags: %s (%d)", flags, combinedFlags);
+ log.error(errorMsg);
+ throw new RuntimeException(errorMsg); // Use RuntimeException
}
-
- logger.debug("RandomX dataset allocated successfully with flags: {}", flags);
+
+ log.info("RandomX dataset allocated successfully at pointer: {} with flags: {}", Pointer.nativeValue(datasetPointer), flags);
}
/**
* Initializes the dataset using multiple threads.
- * The initialization work is divided equally among threads based on available CPU cores.
- * Each thread initializes its assigned portion of the dataset items.
+ * The initialization work is divided among threads based on available CPU cores.
*
- * @param cache The RandomXCache instance used to initialize the dataset
- * @throws RuntimeException if the initialization is interrupted
+ * @param cache The RandomXCache instance required for dataset initialization.
+ * @throws RuntimeException if initialization is interrupted or fails.
+ * @throws IllegalStateException if the dataset is not allocated.
*/
public void init(RandomXCache cache) {
- if (cache == null) {
- throw new IllegalArgumentException("Cache cannot be null");
+ if (datasetPointer == null) {
+ throw new IllegalStateException("Dataset is not allocated.");
+ }
+ if (cache == null || cache.getCachePointer() == null) {
+ throw new IllegalArgumentException("Valid cache instance with allocated cache pointer is required for dataset initialization.");
}
-
+
long startTime = System.nanoTime();
-
- // Get total items count
- long totalItems = RandomXJNALoader.getInstance().randomx_dataset_item_count().longValue();
-
- // Calculate optimal thread count
+
+ // Get total items count using RandomXNative
+ long totalItems = RandomXNative.randomx_dataset_item_count().longValue();
+ if (totalItems <= 0) {
+ log.warn("RandomX dataset item count is zero or negative ({}). Skipping initialization.", totalItems);
+ return; // No items to initialize
+ }
+
+ // Calculate optimal thread count (using half of available processors by default)
int availableProcessors = Runtime.getRuntime().availableProcessors();
- // Default to half of available processors, but can be configured
int initThreadCount = Math.max(1, availableProcessors / 2);
-
- logger.info("Initializing dataset with {} threads for {} items", initThreadCount, totalItems);
-
- // Create thread pool
+ log.info("Initializing dataset ({} items) using {} threads.", totalItems, initThreadCount);
+
+ // Create thread pool with custom thread factory for naming
ExecutorService executor = Executors.newFixedThreadPool(initThreadCount, new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
@@ -113,66 +125,74 @@ public Thread newThread(Runnable r) {
});
try {
- // Calculate items per thread - ensure even distribution
- long perThread = totalItems / initThreadCount;
+ // Calculate items per thread and handle remainder
+ long itemsPerThread = totalItems / initThreadCount;
long remainder = totalItems % initThreadCount;
List> futures = new ArrayList<>(initThreadCount);
-
- // Submit initialization tasks
- long startItem = 0;
+ long currentItemStart = 0;
+
+ // Submit initialization tasks for each thread
for (int i = 0; i < initThreadCount; i++) {
- // Add remainder items to the last thread
- long itemCount = perThread + (i == initThreadCount - 1 ? remainder : 0);
- final long start = startItem;
+ long itemCount = itemsPerThread + (i < remainder ? 1 : 0); // Distribute remainder evenly
+ if (itemCount == 0) continue; // Skip threads with no work
+
+ final long start = currentItemStart;
final long count = itemCount;
-
+ final Pointer cachePtr = cache.getCachePointer(); // Pass cache pointer explicitly
+
futures.add(executor.submit(() -> {
+ String threadName = Thread.currentThread().getName();
try {
- logger.debug("Thread {} initializing items [{}, {})",
- Thread.currentThread().getName(), start, start + count);
-
- RandomXJNALoader.getInstance().randomx_init_dataset(
- dataset,
- cache.getCachePointer(),
+ log.debug("{} starting initialization for items [{}, {})", threadName, start, start + count);
+ // Use RandomXNative for dataset initialization
+ RandomXNative.randomx_init_dataset(
+ datasetPointer,
+ cachePtr,
new NativeLong(start),
new NativeLong(count)
);
-
- logger.debug("Thread {} completed initialization of {} items",
- Thread.currentThread().getName(), count);
+ log.debug("{} finished initialization for items [{}, {})", threadName, start, start + count);
} catch (Exception e) {
- logger.error("Dataset initialization failed for range [{}, {})", start, start + count, e);
- throw new RuntimeException("Dataset initialization failed", e);
+ log.error("{} failed during initialization for items [{}, {}). Error: {}",
+ threadName, start, start + count, e.getMessage(), e);
+ // Propagate exception to be caught by future.get()
+ throw new RuntimeException("Dataset initialization failed in thread " + threadName, e);
}
}));
-
- startItem += itemCount;
+ currentItemStart += itemCount;
}
-
- // Wait for all threads to complete
+
+ // Wait for all threads to complete and check for exceptions
for (Future> future : futures) {
try {
- future.get();
+ future.get(); // Throws ExecutionException if the task threw an exception
} catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ Thread.currentThread().interrupt(); // Preserve interrupt status
+ log.error("Dataset initialization interrupted.", e);
throw new RuntimeException("Dataset initialization interrupted", e);
} catch (ExecutionException e) {
+ log.error("Dataset initialization failed.", e.getCause());
+ // Unwrap the original exception thrown by the task
throw new RuntimeException("Dataset initialization failed", e.getCause());
}
}
-
- long duration = (System.nanoTime() - startTime) / 1_000_000;
- logger.info("Dataset initialization completed in {} ms", duration);
-
+
+ long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ log.info("Dataset initialization completed successfully in {} ms.", durationMillis);
+
} finally {
+ // Shutdown executor service gracefully
executor.shutdown();
try {
- if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
+ // Wait a reasonable time for tasks to finish
+ if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
+ log.warn("Executor did not terminate in 60 seconds. Forcing shutdown.");
executor.shutdownNow();
}
} catch (InterruptedException e) {
+ log.error("Interrupted while waiting for executor termination.", e);
executor.shutdownNow();
- Thread.currentThread().interrupt();
+ Thread.currentThread().interrupt(); // Preserve interrupt status
}
}
}
@@ -180,21 +200,31 @@ public Thread newThread(Runnable r) {
/**
* Gets the pointer to the allocated dataset memory.
*
- * @return Pointer to the dataset memory
+ * @return Pointer to the dataset memory.
+ * @throws IllegalStateException if the dataset is not allocated.
*/
- public Pointer getPointer() {
- return dataset;
+ public Pointer getDatasetPointer() {
+ if (datasetPointer == null) {
+ throw new IllegalStateException("Dataset is not allocated.");
+ }
+ return datasetPointer;
}
- /**
+ /**
* Releases the allocated dataset memory.
* This method is called automatically when using try-with-resources.
*/
@Override
public void close() {
- if (dataset != null) {
- RandomXJNALoader.getInstance().randomx_release_dataset(dataset);
- logger.debug("RandomX dataset released successfully");
+ if (datasetPointer != null) {
+ log.debug("Releasing RandomX dataset at pointer: {}", Pointer.nativeValue(datasetPointer));
+ try {
+ // Use RandomXNative for release
+ RandomXNative.randomx_release_dataset(datasetPointer);
+ log.info("RandomX dataset released successfully.");
+ } catch (Throwable t) {
+ log.error("Error occurred while releasing RandomX dataset. Pointer: {}", Pointer.nativeValue(datasetPointer), t);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXJNA.java b/src/main/java/io/xdag/crypto/randomx/RandomXJNA.java
deleted file mode 100644
index 38bdf6b..0000000
--- a/src/main/java/io/xdag/crypto/randomx/RandomXJNA.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2022-2030 The XdagJ Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package io.xdag.crypto.randomx;
-
-import com.sun.jna.Library;
-import com.sun.jna.NativeLong;
-import com.sun.jna.Pointer;
-
-/**
- * JNA interface for the RandomX native library functions.
- * This interface provides direct access to the native RandomX functions through JNA.
- */
-public interface RandomXJNA extends Library {
-
- /**
- * Gets the recommended flags for the current CPU.
- * These flags are determined based on CPU features and capabilities.
- *
- * @return Integer value representing the recommended flags
- */
- int randomx_get_flags();
-
- /**
- * Allocates memory for a RandomX cache.
- * The allocated memory must be released using randomx_release_cache.
- *
- * @param flags Configuration flags for the cache
- * @return Pointer to the allocated cache memory, or null if allocation fails
- */
- Pointer randomx_alloc_cache(int flags);
-
- /**
- * Initializes a RandomX cache with the provided key.
- * This operation is required before the cache can be used.
- *
- * @param cache Pointer to the allocated cache memory
- * @param key Pointer to the key data
- * @param keySize Size of the key in bytes
- */
- void randomx_init_cache(Pointer cache, Pointer key, long keySize);
-
- /**
- * Releases the memory allocated for a RandomX cache.
- * This function must be called to prevent memory leaks.
- *
- * @param cache Pointer to the cache memory to be released
- */
- void randomx_release_cache(Pointer cache);
-
- /**
- * Creates a new RandomX virtual machine instance.
- * The instance must be destroyed using randomx_destroy_vm when no longer needed.
- *
- * @param flags Configuration flags for the VM
- * @param cache Pointer to the initialized cache
- * @param dataset Pointer to the initialized dataset, or null if not using a dataset
- * @return Pointer to the created VM instance, or null if creation fails
- */
- Pointer randomx_create_vm(int flags, Pointer cache, Pointer dataset);
-
- /**
- * Destroys a RandomX virtual machine instance.
- * This function must be called to prevent memory leaks.
- *
- * @param machine Pointer to the VM instance to be destroyed
- */
- void randomx_destroy_vm(Pointer machine);
-
- /**
- * Allocates memory for a RandomX dataset.
- * The allocated memory must be released using randomx_release_dataset.
- *
- * @param flags Configuration flags for the dataset
- * @return Pointer to the allocated dataset memory, or null if allocation fails
- */
- Pointer randomx_alloc_dataset(int flags);
-
- /**
- * Initializes items in a RandomX dataset.
- * This operation can be performed in parallel by multiple threads.
- *
- * @param dataset Pointer to the dataset memory
- * @param cache Pointer to the initialized cache
- * @param startItem Index of the first item to initialize
- * @param itemCount Number of items to initialize
- */
- void randomx_init_dataset(Pointer dataset, Pointer cache, NativeLong startItem, NativeLong itemCount);
-
- /**
- * Gets the number of items in a RandomX dataset.
- *
- * @return Number of items in the dataset
- */
- NativeLong randomx_dataset_item_count();
-
- /**
- * Releases the memory allocated for a RandomX dataset.
- * This function must be called to prevent memory leaks.
- *
- * @param dataset Pointer to the dataset memory to be released
- */
- void randomx_release_dataset(Pointer dataset);
-
- /**
- * Updates the cache used by a RandomX VM instance.
- *
- * @param machine Pointer to the VM instance
- * @param cache Pointer to the new cache
- */
- void randomx_vm_set_cache(Pointer machine, Pointer cache);
-
- /**
- * Updates the dataset used by a RandomX VM instance.
- *
- * @param machine Pointer to the VM instance
- * @param dataset Pointer to the new dataset
- */
- void randomx_vm_set_dataset(Pointer machine, Pointer dataset);
-
- /**
- * Calculates a RandomX hash value.
- *
- * @param machine Pointer to the VM instance
- * @param input Pointer to the input data
- * @param inputSize Size of the input data in bytes
- * @param output Pointer to the output buffer (32 bytes)
- */
- void randomx_calculate_hash(Pointer machine, Pointer input, long inputSize, Pointer output);
-
- /**
- * Begins a multi-part RandomX hashing operation.
- *
- * @param machine Pointer to the VM instance
- * @param input Pointer to the input data
- * @param inputSize Size of the input data in bytes
- */
- void randomx_calculate_hash_first(Pointer machine, Pointer input, long inputSize);
-
- /**
- * Continues a multi-part RandomX hashing operation.
- *
- * @param machine Pointer to the VM instance
- * @param nextInput Pointer to the next input data
- * @param nextInputSize Size of the next input data in bytes
- * @param output Pointer to the output buffer (32 bytes)
- */
- void randomx_calculate_hash_next(Pointer machine, Pointer nextInput, long nextInputSize, Pointer output);
-
- /**
- * Finalizes a multi-part RandomX hashing operation.
- *
- * @param machine Pointer to the VM instance
- * @param output Pointer to the output buffer (32 bytes)
- */
- void randomx_calculate_hash_last(Pointer machine, Pointer output);
-
- /**
- * Calculates a RandomX commitment hash.
- *
- * @param input Pointer to the input data
- * @param inputSize Size of the input data in bytes
- * @param hash_in Pointer to the input hash
- * @param com_out Pointer to the output commitment buffer
- */
- void randomx_calculate_commitment(Pointer input, long inputSize, Pointer hash_in, Pointer com_out);
-}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXJNALoader.java b/src/main/java/io/xdag/crypto/randomx/RandomXJNALoader.java
deleted file mode 100644
index f0d527f..0000000
--- a/src/main/java/io/xdag/crypto/randomx/RandomXJNALoader.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2022-2030 The XdagJ Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package io.xdag.crypto.randomx;
-
-import com.sun.jna.Native;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.File;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-
-/**
- * A singleton class responsible for loading and managing the RandomX native library using JNA.
- * This class handles the platform-specific library loading and provides access to the RandomX native functions.
- */
-public final class RandomXJNALoader {
-
- /**
- * The singleton instance of the RandomX JNA interface
- */
- private static volatile RandomXJNA instance;
-
- /**
- * Lock object for thread synchronization
- */
- private static final Object LOCK = new Object();
-
- /**
- * Private constructor to prevent instantiation
- */
- private RandomXJNALoader() {
- // Prevent instantiation
- }
-
- static {
- init();
- }
-
- /**
- * Initializes the RandomX native library
- */
- public static void init() {
- loadLibrary("librandomx");
- }
-
- /**
- * Gets or creates the singleton instance of the RandomX JNA interface.
- * Uses double-checked locking for thread safety and better performance.
- *
- * @return The singleton instance of RandomXJNA
- */
- public static RandomXJNA getInstance() {
- RandomXJNA result = instance;
- if (result == null) {
- synchronized (LOCK) {
- result = instance;
- if (result == null) {
- String osName = System.getProperty("os.name").toLowerCase();
- if (osName.contains("win")) {
- String libFilePath = "native/librandomx_windows_x86_64.dll";
- instance = result = Native.load(libFilePath, RandomXJNA.class);
- } else {
- instance = result = Native.load("randomx", RandomXJNA.class);
- }
- }
- }
- }
- return result;
- }
-
- /**
- * Loads the native library for the current platform and architecture.
- * Supports Windows, macOS, and Linux (x86_64) platforms.
- * The library is extracted from resources to a temporary file before loading.
- *
- * @param libraryName The base name of the library to load
- * @throws UnsupportedOperationException if the current platform is not supported
- * @throws RuntimeException if the library loading fails
- */
- public static void loadLibrary(String libraryName) {
- String os = System.getProperty("os.name").toLowerCase();
- String arch = System.getProperty("os.arch").toLowerCase();
- String libFileName = getLibraryFileName(libraryName, os, arch);
-
- // Load from resources
- try (InputStream libStream = getLibraryStream(libFileName)) {
- File tempFile = createTempLibraryFile(libraryName, libFileName);
- Files.copy(libStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
- System.load(tempFile.getAbsolutePath());
- } catch (Exception e) {
- throw new RuntimeException("Failed to load native library: " + libraryName, e);
- }
- }
-
- /**
- * Gets the platform-specific library file name.
- *
- * @param libraryName Base name of the library
- * @param os Operating system name
- * @param arch System architecture
- * @return The complete library file name
- * @throws UnsupportedOperationException if the platform is not supported
- */
- private static String getLibraryFileName(String libraryName, String os, String arch) {
- if (os.contains("win")) {
- if (StringUtils.containsAny(arch, "amd64", "x86_64")) {
- return String.format("native/%s_windows_x86_64.dll", libraryName);
- }
- } else if (os.contains("mac")) {
- return String.format("native/%s_macos_%s.dylib", libraryName, arch);
- } else if (StringUtils.contains(os, "linux")) {
- if (StringUtils.containsAny(arch, "amd64", "x86_64")) {
- return String.format("native/%s_linux_x86_64.so", libraryName);
- }
- }
- throw new UnsupportedOperationException(
- String.format("Unsupported platform: OS=%s, Architecture=%s", os, arch));
- }
-
- /**
- * Gets the input stream for the library resource.
- *
- * @param libFileName Library file name
- * @return InputStream for the library resource
- * @throws IllegalStateException if the library resource is not found
- */
- private static InputStream getLibraryStream(String libFileName) {
- InputStream libStream = RandomXJNALoader.class.getClassLoader().getResourceAsStream(libFileName);
- if (libStream == null) {
- throw new IllegalStateException("Native library not found: " + libFileName);
- }
- return libStream;
- }
-
- /**
- * Creates a temporary file for the native library.
- *
- * @param libraryName Base name of the library
- * @param libFileName Complete library file name
- * @return Temporary File object
- * @throws RuntimeException if file creation fails
- */
- private static File createTempLibraryFile(String libraryName, String libFileName) {
- try {
- File tempFile = File.createTempFile(
- libraryName,
- libFileName.substring(libFileName.lastIndexOf('.')));
- tempFile.deleteOnExit();
- return tempFile;
- } catch (Exception e) {
- throw new RuntimeException("Failed to create temporary library file", e);
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXLibraryLoader.java b/src/main/java/io/xdag/crypto/randomx/RandomXLibraryLoader.java
new file mode 100644
index 0000000..fa93b89
--- /dev/null
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXLibraryLoader.java
@@ -0,0 +1,203 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2022-2030 The XdagJ Developers
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.xdag.crypto.randomx;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Handles the loading of the RandomX native library.
+ * This class is responsible for extracting the library from resources if necessary,
+ * loading it into the JVM, and configuring JNA library paths.
+ */
+@Slf4j
+final class RandomXLibraryLoader {
+
+ private static boolean isLoaded = false;
+ private static String loadedLibraryPath = null; // Store the path of the loaded library for logging
+
+ // Private constructor to prevent instantiation
+ private RandomXLibraryLoader() {}
+
+ /**
+ * Ensures that the RandomX native library is loaded.
+ * This method is synchronized and will only attempt to load the library once.
+ * It extracts the library from resources, loads it using System.load(),
+ * and sets the jna.library.path.
+ *
+ * @throws UnsatisfiedLinkError if the library cannot be loaded for any reason.
+ * @throws Exception for other unexpected errors during loading.
+ */
+ public static synchronized void load() throws Exception {
+ if (isLoaded) {
+ log.info("Native library already loaded from: {}", loadedLibraryPath);
+ return;
+ }
+
+ try {
+ File tempFile = extractAndLoadNativeLibrary();
+ loadedLibraryPath = tempFile.getAbsolutePath();
+
+ String tempLibDir = tempFile.getParent();
+ if (tempLibDir != null) {
+ String currentJnaPath = System.getProperty("jna.library.path");
+ if (currentJnaPath == null || currentJnaPath.isEmpty()) {
+ System.setProperty("jna.library.path", tempLibDir);
+ } else if (!currentJnaPath.contains(tempLibDir)) {
+ System.setProperty("jna.library.path", currentJnaPath + File.pathSeparator + tempLibDir);
+ }
+ log.info("Set jna.library.path to include: {}", tempLibDir);
+ } else {
+ log.warn("Could not get parent directory for temporary library file: {}", loadedLibraryPath);
+ }
+
+ isLoaded = true;
+ log.info("RandomX native library loaded successfully via RandomXLibraryLoader from: {}", loadedLibraryPath);
+
+ } catch (UnsatisfiedLinkError ule) { // Catch specifically from System.load()
+ log.error("Failed to load native library (UnsatisfiedLinkError from System.load()): {}: {}",
+ (loadedLibraryPath != null ? loadedLibraryPath : ""), ule.getMessage(), ule);
+ logLibraryPaths(); // Log paths for diagnostics
+ throw ule; // Re-throw to be handled by RandomXNative's static block
+ } catch (Exception e) {
+ log.error("An unexpected error occurred during native library loading: {}", e.getMessage(), e);
+ logLibraryPaths(); // Log paths for diagnostics
+ throw e; // Re-throw to be handled by RandomXNative's static block
+ }
+ }
+
+ /**
+ * Logs current JNA and Java library paths for diagnostic purposes.
+ */
+ public static void logLibraryPaths() {
+ String libPath = System.getProperty("jna.library.path");
+ log.error("Current jna.library.path: {}", (libPath != null ? libPath : ""));
+ String javaLibPath = System.getProperty("java.library.path");
+ log.error("Current java.library.path: {}", (javaLibPath != null ? javaLibPath : ""));
+ }
+
+ /**
+ * Extracts the native library from resources to a temporary file and loads it using System.load().
+ * @return The File object of the loaded temporary library.
+ * @throws IOException if file operations fail.
+ * @throws IllegalStateException if the resource is not found.
+ * @throws UnsatisfiedLinkError if System.load() fails.
+ */
+ private static File extractAndLoadNativeLibrary() throws IOException, IllegalStateException, UnsatisfiedLinkError {
+ String libraryLogicalName = "randomx";
+ String resourceBaseName = "librandomx";
+
+ String os = System.getProperty("os.name", "").toLowerCase();
+ String arch = System.getProperty("os.arch", "").toLowerCase();
+
+ String libFileNameInResources = getPlatformSpecificResourceName(resourceBaseName, os, arch);
+
+ Path tempFilePath = null;
+ try (InputStream libStream = RandomXLibraryLoader.class.getClassLoader().getResourceAsStream(libFileNameInResources)) {
+ if (libStream == null) {
+ log.error("Native library resource not found: {}", libFileNameInResources);
+ try {
+ java.net.URL resourceUrl = RandomXLibraryLoader.class.getResource("");
+ if (resourceUrl != null) {
+ log.error("Checked relative to class location: {}", resourceUrl);
+ }
+ java.net.URL rootResourceUrl = RandomXLibraryLoader.class.getClassLoader().getResource("");
+ if (rootResourceUrl != null) {
+ log.error("Checked relative to classloader root: {}", rootResourceUrl);
+ }
+ } catch(Exception e) { /* ignore secondary errors */ }
+
+ throw new IllegalStateException("Native library resource not found: " + libFileNameInResources +
+ ". Check classpath and resource packaging.");
+ }
+
+ String mappedLibName = System.mapLibraryName(libraryLogicalName);
+ String tempFilePrefix = mappedLibName.substring(0, mappedLibName.lastIndexOf('.'));
+ String tempFileSuffix = mappedLibName.substring(mappedLibName.lastIndexOf('.'));
+
+ tempFilePath = Files.createTempFile(tempFilePrefix + "-", tempFileSuffix);
+ File tempFile = tempFilePath.toFile();
+ tempFile.deleteOnExit();
+
+ Files.copy(libStream, tempFilePath, StandardCopyOption.REPLACE_EXISTING);
+
+ System.load(tempFile.getAbsolutePath()); // This can throw UnsatisfiedLinkError
+ log.info("Native library extracted to and loaded from: {} (resource: {})", tempFile.getAbsolutePath(), libFileNameInResources);
+ return tempFile;
+
+ } catch (IOException e) {
+ log.error("IOException during library extraction from resource '{}': {}", libFileNameInResources, e.getMessage(), e);
+ if (tempFilePath != null) {
+ log.error("Temporary file path was: {}", tempFilePath.toString());
+ }
+ throw e;
+ } catch (UnsatisfiedLinkError ule) { // Catch from System.load()
+ log.error("UnsatisfiedLinkError during System.load() for '{}': {}",
+ (tempFilePath != null ? tempFilePath.toString() : libFileNameInResources), ule.getMessage(), ule);
+ throw ule; // Re-throw
+ } catch (Exception e) { // Catch other unexpected errors
+ log.error("Unexpected exception during library extraction/loading from resource '{}': {}",
+ libFileNameInResources, e.getMessage(), e);
+ // Wrap in a more specific runtime exception if desired, or re-throw as is.
+ throw new RuntimeException("Failed to load native library from resource: " + libFileNameInResources, e);
+ }
+ }
+
+ /**
+ * Constructs the platform-specific library file name as it exists in the resources.
+ */
+ private static String getPlatformSpecificResourceName(String resourceBaseName, String os, String arch) {
+ String prefix = "native/";
+ String suffix;
+ String platformArch = arch;
+
+ if (os.contains("win")) {
+ suffix = "_windows_x86_64.dll";
+ } else if (os.contains("mac")) {
+ if ("arm64".equals(arch)) {
+ platformArch = "aarch64";
+ } else if (!"aarch64".equals(arch) && !"x86_64".equals(arch)){
+ log.warn("Uncommon macOS arch detected: {}, attempting to use it directly in resource path.", arch);
+ }
+ suffix = "_macos_" + platformArch + ".dylib";
+ } else if (os.contains("linux")) {
+ if ("amd64".equals(arch)) {
+ platformArch = "x86_64";
+ }
+ suffix = "_linux_" + platformArch + ".so";
+ } else {
+ throw new UnsupportedOperationException(
+ String.format("Unsupported platform: OS=%s, Architecture=%s", os, arch));
+ }
+ String resourcePath = prefix + resourceBaseName + suffix;
+ log.info("Determined resource path: {} (OS: {}, Arch: {} -> {})",
+ resourcePath, os, arch, platformArch);
+ return resourcePath;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXNative.java b/src/main/java/io/xdag/crypto/randomx/RandomXNative.java
new file mode 100644
index 0000000..0596f19
--- /dev/null
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXNative.java
@@ -0,0 +1,243 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2022-2030 The XdagJ Developers
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.xdag.crypto.randomx;
+
+import com.sun.jna.Native;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+
+/**
+ * JNA direct mapping class for RandomX native library functions.
+ * Native library loading is handled by {@link RandomXLibraryLoader}.
+ * This class registers the native methods after the library is successfully loaded.
+ */
+public class RandomXNative {
+ static {
+ try {
+ // Step 1: Initialize the library using the loader.
+ // This will handle extraction, loading (System.load), and setting jna.library.path.
+ RandomXLibraryLoader.load();
+
+ // Step 2: Register the native methods with JNA.
+ // "randomx" is the logical name. JNA should find the loaded library
+ // via the jna.library.path set by the loader, or because it's already loaded.
+ Native.register("randomx");
+
+ // Simple log to confirm registration attempt after loader success.
+ // For more detailed logging, rely on RandomXLibraryLoader or a dedicated logging framework.
+ System.out.println("[INFO] RandomXNative: Native methods registration attempted for 'randomx'.");
+
+ } catch (UnsatisfiedLinkError ule) {
+ // Logged by RandomXLibraryLoader, but good to catch and re-throw here
+ // to ensure the class loading fails clearly if registration itself has an issue
+ // or if the library loaded but methods can't be bound.
+ System.err.println("[ERROR] RandomXNative: Failed to link native library or register methods. " +
+ "Ensure 'randomx' library was loaded correctly by RandomXLibraryLoader. " +
+ "Error: " + ule.getMessage());
+ ule.printStackTrace(System.err); // Print stack trace for more details
+ throw ule; // Re-throw to indicate critical failure
+ } catch (Exception e) {
+ System.err.println("[ERROR] RandomXNative: An unexpected error occurred during static initialization: " + e.getMessage());
+ e.printStackTrace(System.err);
+ throw new RuntimeException("Failed to initialize RandomXNative due to an unexpected error", e);
+ }
+ }
+
+ // --- Native Method Declarations ---
+ // Add all the necessary methods you intend to call directly
+
+ /**
+ * Retrieves the RandomX flags supported by the current CPU.
+ * @return An integer value representing the combination of supported flags.
+ * @see RandomXFlag
+ */
+ public static native int randomx_get_flags();
+
+ /**
+ * Allocates a RandomX cache object.
+ * This cache is used to store precomputed data to speed up hash calculations, especially in "fast" mode.
+ * It needs to be initialized using {@link #randomx_init_cache(Pointer, Pointer, long)}.
+ * After use, it should be released via {@link #randomx_release_cache(Pointer)}.
+ *
+ * @param flags Flags used to initialize the cache. See {@link RandomXFlag}.
+ * @return A pointer to the allocated RandomX cache. Behavior is undefined if allocation fails.
+ */
+ public static native Pointer randomx_alloc_cache(int flags);
+
+ /**
+ * Initializes a RandomX cache previously allocated by {@link #randomx_alloc_cache(int)}.
+ *
+ * @param cache Pointer to the cache object obtained from {@code randomx_alloc_cache}.
+ * @param key The key (seed) used to initialize the cache.
+ * @param keySize The length of the key in bytes.
+ */
+ public static native void randomx_init_cache(Pointer cache, Pointer key, long keySize);
+
+ /**
+ * Releases a RandomX cache previously allocated by {@link #randomx_alloc_cache(int)}.
+ *
+ * @param cache Pointer to the cache object to be released.
+ */
+ public static native void randomx_release_cache(Pointer cache);
+
+ // --- Dataset related methods ---
+
+ /**
+ * Allocates a RandomX dataset object.
+ * The dataset is used for "slow" hashing mode and requires a large amount of memory.
+ * It needs to be initialized using {@link #randomx_init_dataset(Pointer, Pointer, NativeLong, NativeLong)}.
+ * After use, it should be released via {@link #randomx_release_dataset(Pointer)}.
+ *
+ * @param flags Flags used to initialize the dataset. See {@link RandomXFlag}.
+ * @return A pointer to the allocated RandomX dataset. Behavior is undefined if allocation fails.
+ */
+ public static native Pointer randomx_alloc_dataset(int flags);
+
+ /**
+ * Gets the number of items required to build a full dataset.
+ * @return The total number of items in the dataset, as an unsigned long represented by {@link NativeLong}.
+ */
+ public static native NativeLong randomx_dataset_item_count(); // Returns unsigned long, use NativeLong
+
+ /**
+ * Initializes a portion of a RandomX dataset previously allocated by {@link #randomx_alloc_dataset(int)}.
+ * This process is typically done in chunks, as the full dataset can be very large.
+ *
+ * @param dataset Pointer to the dataset object obtained from {@code randomx_alloc_dataset}.
+ * @param cache Pointer to an initialized RandomX cache.
+ * @param startItem The starting item index (0-based) of the dataset to initialize.
+ * @param itemCount The number of items to initialize, starting from {@code startItem}.
+ */
+ public static native void randomx_init_dataset(Pointer dataset, Pointer cache, NativeLong startItem, NativeLong itemCount);
+
+ /**
+ * Releases a RandomX dataset previously allocated by {@link #randomx_alloc_dataset(int)}.
+ *
+ * @param dataset Pointer to the dataset object to be released.
+ */
+ public static native void randomx_release_dataset(Pointer dataset);
+
+ // --- VM related methods ---
+
+ /**
+ * Creates a RandomX virtual machine (VM).
+ * The VM is used to perform hash calculations.
+ * It can be configured for "fast" mode (using a cache) or "slow" mode (using a dataset).
+ * After use, it should be destroyed via {@link #randomx_destroy_vm(Pointer)}.
+ *
+ * @param flags Flags used to create the VM. See {@link RandomXFlag}.
+ * @param cache Pointer to an initialized RandomX cache. Can be {@code null} if using "slow" mode.
+ * @param dataset Pointer to an initialized RandomX dataset. Can be {@code null} if using "fast" mode.
+ * @return A pointer to the created RandomX VM. Behavior is undefined if creation fails.
+ */
+ public static native Pointer randomx_create_vm(int flags, Pointer cache, Pointer dataset); // dataset can be NULL for light VM
+
+ /**
+ * Changes the cache used by an existing RandomX VM.
+ *
+ * @param vm Pointer to the VM to be modified.
+ * @param cache Pointer to the new initialized RandomX cache.
+ */
+ public static native void randomx_vm_set_cache(Pointer vm, Pointer cache);
+
+ /**
+ * Changes the dataset used by an existing RandomX VM.
+ *
+ * @param vm Pointer to the VM to be modified.
+ * @param dataset Pointer to the new initialized RandomX dataset. Can be {@code null} to switch to "fast" mode (if supported by the VM).
+ */
+ public static native void randomx_vm_set_dataset(Pointer vm, Pointer dataset); // dataset can be NULL
+
+ /**
+ * Destroys a RandomX VM previously created by {@link #randomx_create_vm(int, Pointer, Pointer)}.
+ *
+ * @param vm Pointer to the VM to be destroyed.
+ */
+ public static native void randomx_destroy_vm(Pointer vm);
+
+ /**
+ * Calculates the RandomX hash of the input data using the specified VM.
+ *
+ * @param vm Pointer to an initialized VM.
+ * @param input Pointer to the input data to be hashed.
+ * @param inputSize Size of the input data in bytes.
+ * @param output Pointer to a buffer where the calculated hash will be stored. The buffer size should be {@link RandomXUtils#RANDOMX_HASH_SIZE}.
+ */
+ public static native void randomx_calculate_hash(Pointer vm, Pointer input, long inputSize, Pointer output);
+
+ // --- Multi-part hashing methods ---
+
+ /**
+ * Starts a multi-part RandomX hash calculation.
+ * This is the first step in a multi-part hash, typically used for streaming data or very large inputs.
+ *
+ * @param vm Pointer to an initialized VM.
+ * @param input Pointer to the first part of the input data.
+ * @param inputSize Size of the first part of the input data in bytes.
+ */
+ public static native void randomx_calculate_hash_first(Pointer vm, Pointer input, long inputSize);
+
+ /**
+ * Continues a multi-part RandomX hash calculation, processing the next chunk of data.
+ * Must be called after {@link #randomx_calculate_hash_first(Pointer, Pointer, long)}.
+ * Can be called multiple times for sequential data chunks.
+ *
+ * @param vm Pointer to the VM undergoing multi-part hashing.
+ * @param input Pointer to the next block of input data.
+ * @param inputSize Size of the next block of input data in bytes.
+ * @param output Pointer to a buffer for storing an intermediate (or final, if no subsequent 'last' call) hash.
+ * Note: The RandomX C API's randomx_calculate_hash_next doesn't always output the full hash directly;
+ * its behavior may depend on the specific implementation.
+ * Typically, the final hash is obtained via {@link #randomx_calculate_hash_last(Pointer, Pointer)}.
+ */
+ public static native void randomx_calculate_hash_next(Pointer vm, Pointer input, long inputSize, Pointer output);
+
+ /**
+ * Finalizes a multi-part RandomX hash calculation and retrieves the final hash value.
+ * Must be called after {@link #randomx_calculate_hash_first(Pointer, Pointer, long)} and any number of
+ * {@link #randomx_calculate_hash_next(Pointer, Pointer, long, Pointer)} calls.
+ *
+ * @param vm Pointer to the VM undergoing multi-part hashing.
+ * @param output Pointer to a buffer where the final calculated hash will be stored. The buffer size should be {@link RandomXUtils#RANDOMX_HASH_SIZE}.
+ */
+ public static native void randomx_calculate_hash_last(Pointer vm, Pointer output);
+
+ // --- Commitment hash method (assuming this C API exists as used) ---
+
+ /**
+ * Calculates a commitment value for a two-phase commit scheme.
+ * This is an optimization specific to certain PoW variants, allowing pre-computation of part of the work
+ * without knowing the full input.
+ * Note: This method operates directly on input data and a pre-calculated hash, not through a VM.
+ *
+ * @param input Pointer to the original input data (e.g., the first part of a block header).
+ * @param inputSize Size of the original input data in bytes.
+ * @param hash_in Pointer to a pre-calculated hash of some transformation of the original input
+ * (e.g., a hash of the first part of the block header).
+ * This hash will be part of the commitment calculation.
+ * @param com_out Pointer to a buffer where the calculated commitment will be stored. The buffer size should be {@link RandomXUtils#RANDOMX_HASH_SIZE}.
+ */
+ public static native void randomx_calculate_commitment(Pointer input, long inputSize, Pointer hash_in, Pointer com_out);
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXTemplate.java b/src/main/java/io/xdag/crypto/randomx/RandomXTemplate.java
index f73d583..509b8ab 100644
--- a/src/main/java/io/xdag/crypto/randomx/RandomXTemplate.java
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXTemplate.java
@@ -26,6 +26,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
import java.util.*;
@@ -35,9 +36,10 @@
*/
@Builder
@ToString
+@Slf4j
public class RandomXTemplate implements AutoCloseable {
-
/** Flag indicating if the template is in mining mode */
+ @Getter
private final boolean miningMode;
/** Set of RandomX flags for configuring the algorithm behavior */
@@ -45,124 +47,216 @@ public class RandomXTemplate implements AutoCloseable {
private final Set flags;
/** Cache for RandomX operations */
+ @Getter
private final RandomXCache cache;
/** Dataset for RandomX mining operations */
+ @Getter
private RandomXDataset dataset;
/** Virtual machine instance for RandomX operations */
+ @Getter
private RandomXVM vm;
+ /** Stores the current key used for cache initialization to avoid redundant re-initializations. */
+ @Getter
+ private byte[] currentKey;
+
/**
- * Initializes the RandomX cache or dataset based on the mining mode.
- * If in mining mode, enables FULL_MEM flag and initializes dataset.
- * Otherwise, removes FULL_MEM flag and sets dataset to null.
+ * Initializes the RandomX virtual machine (VM) with the configured settings.
+ * This method must be called before any hash calculation.
+ * If in mining mode, the dataset should be initialized before calling this method,
+ * and if in light mode, the cache should be initialized.
*/
public void init() {
Set vmFlags = EnumSet.copyOf(flags);
if (miningMode) {
vmFlags.add(RandomXFlag.FULL_MEM);
- dataset = new RandomXDataset(vmFlags);
- dataset.init(cache);
+ // Ensure cache is initialized with currentKey before creating dataset
+ if (this.currentKey == null) {
+ log.warn("Initializing RandomXTemplate without a key set for the cache. Dataset initialization might rely on an uninitialized cache if not subsequently set.");
+ } else if (cache.getCachePointer() == null) { // Cache might be allocated but not initialized
+ log.warn("Cache pointer is null during init despite currentKey being set. This should not happen if cache is managed correctly.");
+ }
+
+ log.debug("Mining mode enabled. Creating and initializing dataset with flags: {}", vmFlags);
+ dataset = new RandomXDataset(vmFlags); // Dataset uses its own flags, usually including FULL_MEM
+ dataset.init(cache); // Dataset initialization depends on an initialized cache
} else {
vmFlags.remove(RandomXFlag.FULL_MEM);
+ if (dataset != null) {
+ dataset.close(); // Ensure previous dataset is closed if switching modes
+ }
dataset = null;
+ log.debug("Light mode enabled. Dataset will not be used.");
}
+
+ log.debug("Creating RandomXVM with flags: {} (Cache: {}, Dataset: {})",
+ vmFlags,
+ cache != null ? "Present" : "Null",
+ dataset != null ? "Present" : "Null");
vm = new RandomXVM(vmFlags, cache, dataset);
+ log.info("RandomXTemplate initialized. VM created.");
}
/**
- * Changes the current RandomX key by reinitializing the dataset or cache.
- * If the key is unchanged, returns without performing reinitialization.
- *
- * @param key The key to initialize RandomX with (generally a hash)
+ * Changes the current RandomX key by reinitializing the cache and, if in mining mode, the dataset.
+ * If the provided key is the same as the current key, this method returns without reinitialization.
+ *
+ * @param key The new key (typically a seed hash) to initialize RandomX components with.
+ * @throws IllegalArgumentException if the key is null or empty.
*/
public void changeKey(byte[] key) {
if (key == null || key.length == 0) {
- throw new IllegalArgumentException("Key cannot be null or empty");
+ throw new IllegalArgumentException("Key cannot be null or empty for changeKey operation.");
}
- RandomXCache currentCache = vm.getCache();
- // Check if key is unchanged
- if (currentCache != null && currentCache.getCachePointer() != null &&
- currentCache.getKeyPointer() != null &&
- Arrays.equals(key, currentCache.getKeyPointer().getByteArray(0, key.length))) {
+ // Check if the new key is the same as the current key.
+ if (Arrays.equals(this.currentKey, key)) {
+ log.debug("Key is unchanged. Skipping reinitialization.");
return;
}
+ log.info("Changing RandomX key. Old key hash (if any): {}, New key hash: {}",
+ (this.currentKey != null ? Arrays.hashCode(this.currentKey) : "N/A"), Arrays.hashCode(key));
+
+ // Initialize the cache with the new key.
+ // The cache instance itself is final, but its internal state is changed by init().
cache.init(key);
- vm.setCache(cache);
-
+ this.currentKey = Arrays.copyOf(key, key.length); // Store a copy of the new key.
+
+ // If a VM instance exists, update its cache.
+ // If init() hasn't been called yet, vm will be null. The new cache will be used when vm is created in init().
+ if (vm != null) {
+ log.debug("Updating VM with the new cache.");
+ vm.setCache(cache);
+ } else {
+ log.warn("VM is null during changeKey. The new cache will be used upon VM creation in init().");
+ }
+
+ // If in mining mode, the dataset also needs to be reinitialized with the new cache.
if (miningMode) {
+ log.debug("Mining mode: Reinitializing dataset due to key change.");
if (dataset != null) {
- dataset.close();
+ dataset.close(); // Close the old dataset
+ }
+ // Create and initialize a new dataset with the (now re-initialized) cache.
+ // The flags for the dataset should include FULL_MEM.
+ Set datasetFlags = EnumSet.copyOf(this.flags); // Start with base flags
+ datasetFlags.add(RandomXFlag.FULL_MEM);
+
+ dataset = new RandomXDataset(datasetFlags);
+ dataset.init(cache); // Initialize with the cache that has the new key.
+
+ if (vm != null) {
+ log.debug("Updating VM with the new dataset.");
+ vm.setDataset(dataset);
+ } else {
+ // This case should ideally not happen if init() is called after key setting or if builder manages initial key.
+ log.warn("VM is null during dataset reinitialization in changeKey. Dataset will be set when VM is created.");
+ }
+ } else {
+ // In light mode, ensure dataset is null if it was somehow set
+ if (vm != null && vm.getDataset() != null) {
+ log.debug("Light mode: Ensuring VM dataset is null after key change.");
+ vm.setDataset(null);
}
- dataset = new RandomXDataset(flags);
- dataset.init(cache);
- vm.setDataset(dataset);
}
+ log.info("RandomX key changed and components reinitialized successfully.");
}
/**
* Performs a single hash calculation using the RandomX VM.
*
- * @param input Input data for the hash calculation
- * @return A 32-byte array containing the calculated hash
+ * @param input Input data for the hash calculation.
+ * @return A 32-byte array containing the calculated hash.
+ * @throws IllegalStateException if the VM is not initialized.
*/
public byte[] calculateHash(byte[] input) {
+ if (vm == null) {
+ throw new IllegalStateException("RandomX VM is not initialized. Call init() first or ensure key is set.");
+ }
return vm.calculateHash(input);
}
/**
* Begins a multi-part hash calculation by processing the first input.
*
- * @param input Initial input data for the hash calculation
+ * @param input Initial input data for the hash calculation.
+ * @throws IllegalStateException if the VM is not initialized.
*/
public void calculateHashFirst(byte[] input) {
+ if (vm == null) {
+ throw new IllegalStateException("RandomX VM is not initialized. Call init() first or ensure key is set.");
+ }
vm.calculateHashFirst(input);
}
/**
* Continues a multi-part hash calculation by processing the next input.
*
- * @param nextInput Next chunk of input data for the hash calculation
- * @return A 32-byte array containing the intermediate hash result
+ * @param nextInput Next chunk of input data for the hash calculation.
+ * @return A 32-byte array containing the intermediate hash result.
+ * @throws IllegalStateException if the VM is not initialized.
*/
public byte[] calculateHashNext(byte[] nextInput) {
+ if (vm == null) {
+ throw new IllegalStateException("RandomX VM is not initialized. Call init() first or ensure key is set.");
+ }
return vm.calculateHashNext(nextInput);
}
/**
* Finalizes a multi-part hash calculation.
*
- * @return A 32-byte array containing the final hash result
+ * @return A 32-byte array containing the final hash result.
+ * @throws IllegalStateException if the VM is not initialized.
*/
public byte[] calculateHashLast() {
+ if (vm == null) {
+ throw new IllegalStateException("RandomX VM is not initialized. Call init() first or ensure key is set.");
+ }
return vm.calculateHashLast();
}
/**
- * Calculates a commitment hash for the given input string.
+ * Calculates a commitment hash for the given input data.
*
- * @param input The input byte array to calculate commitment for
- * @return A byte array containing the calculated commitment hash
+ * @param input The input byte array to calculate commitment for.
+ * @return A byte array containing the calculated commitment hash.
+ * @throws IllegalStateException if the VM is not initialized.
*/
- public byte[] calcStringCommitment(byte[] input) {
- return vm.calcStringCommitment(input);
+ public byte[] calculateCommitment(byte[] input) {
+ if (vm == null) {
+ throw new IllegalStateException("RandomX VM is not initialized. Call init() first or ensure key is set.");
+ }
+ byte[] hashOfInput = vm.calculateHash(input);
+
+ // Then, use the original input and this calculated hash to get the commitment.
+ return vm.calculateCommitment(input, hashOfInput);
}
/**
- * Releases all allocated resources.
+ * Releases all allocated resources (VM and Dataset).
+ * The Cache is managed externally if passed to the builder, or internally if created by this template.
+ * The Current implementation assumes cache is provided via builder and its lifecycle is managed outside this close().
+ * If RandomXTemplate were to create its own RandomXCache, it should also close it here.
*/
@Override
public void close() {
+ log.debug("Closing RandomXTemplate resources...");
if (vm != null) {
+ log.debug("Closing RandomX VM...");
vm.close();
vm = null;
}
if (dataset != null) {
+ log.debug("Closing RandomX Dataset...");
dataset.close();
dataset = null;
}
+ // currentKey does not need explicit closing.
+ // Cache is not closed here as it's assumed to be managed externally (passed in via builder).
+ log.info("RandomXTemplate resources closed.");
}
}
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXUtils.java b/src/main/java/io/xdag/crypto/randomx/RandomXUtils.java
index 766987a..8298ff7 100644
--- a/src/main/java/io/xdag/crypto/randomx/RandomXUtils.java
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXUtils.java
@@ -23,42 +23,79 @@
*/
package io.xdag.crypto.randomx;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.SystemUtils;
-
import java.util.Set;
+// No SystemUtils or StringUtils needed if we remove platform-specific logic
+import java.util.stream.Collectors;
/**
* Utility class for RandomX constants and helper methods.
- * This class provides static methods to get RandomX flags and flag sets.
+ * This class provides static methods to get RandomX flags.
*/
public final class RandomXUtils {
/**
- * Gets the recommended RandomX flags for the current CPU.
- * This method calls the native RandomX library to determine optimal flags.
- *
- * @return An integer representing the combined RandomX flags
+ * The size of a RandomX hash in bytes (usually 32 bytes).
*/
- public static int getFlags() {
- return RandomXJNALoader.getInstance().randomx_get_flags();
+ public static final int RANDOMX_HASH_SIZE = 32;
+
+ // Use System.out for basic logging to avoid SLF4J dependency for this utility class
+ private static void logInfo(String message) {
+ System.out.println("[INFO] RandomXUtils: " + message);
}
/**
- * Gets a set of RandomX flags appropriate for the current system.
- * This method converts the raw flags to a Set of RandomXFlag enums and
- * applies platform-specific adjustments (e.g., removing JIT flag on macOS).
+ * Gets the recommended RandomX flags from the native library.
*
- * @return A Set of RandomXFlag enums representing the enabled flags
+ * @return An integer representing the combined RandomX flags from the native library.
*/
- public static Set getFlagsSet() {
- int flags = getFlags();
- Set flagsSet = RandomXFlag.fromValue(flags);
- if (SystemUtils.IS_OS_MAC_OSX && StringUtils.containsIgnoreCase(SystemUtils.OS_ARCH, "aarch64")) {
- flagsSet.remove(RandomXFlag.JIT);
+ public static int getNativeFlags() {
+ // Ensure RandomXNative is loaded by accessing it before calling native method
+ try {
+ Class.forName(RandomXNative.class.getName());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("RandomXNative class not found", e);
+ } catch (ExceptionInInitializerError e) {
+ System.err.println("ERROR in RandomXUtils: Failed to initialize RandomXNative: " + e.getCause().getMessage());
+ e.getCause().printStackTrace();
+ throw e; // Re-throw to indicate failure
}
+ return RandomXNative.randomx_get_flags();
+ }
+
+ /**
+ * Gets a set of RandomX flags recommended by the native library.
+ * This method ensures that the DEFAULT flag is included if the native library
+ * returns an empty set or a set that doesn't explicitly include optimizations
+ * that would imply DEFAULT.
+ *
+ * @return A Set of RandomXFlag enums representing the enabled flags.
+ */
+ public static Set getRecommendedFlags() {
+ int nativeFlagsValue = getNativeFlags();
+ logInfo("Native recommended flags value: " + nativeFlagsValue);
+
+ Set flagsSet = RandomXFlag.fromValue(nativeFlagsValue);
+ logInfo("Parsed native flags set: " + flagsSet.stream().map(Enum::name).collect(Collectors.joining(", ")));
+ // Ensure a DEFAULT flag is present if the set is empty or only contains non-functional flags.
+ // The native library should ideally always return DEFAULT (0) or a combination including it
+ // if no other specific flags like JIT are set.
+ // If JIT or other major flags are set, DEFAULT (0) might be implicitly part of the mode.
+ // Let's ensure the set isn't empty and contains DEFAULT if no major operational flags are present.
+ if (flagsSet.isEmpty()) {
+ logInfo("Native flags resulted in an empty set. Adding DEFAULT.");
+ flagsSet.add(RandomXFlag.DEFAULT);
+ } else if (!flagsSet.contains(RandomXFlag.DEFAULT) &&
+ flagsSet.stream().noneMatch(flag ->
+ flag == RandomXFlag.JIT ||
+ flag == RandomXFlag.FULL_MEM ||
+ flag == RandomXFlag.LARGE_PAGES)) {
+ // If no major operational flags are set, and DEFAULT is also missing, add DEFAULT.
+ logInfo("No major operational flags (JIT, FULL_MEM, LARGE_PAGES) or DEFAULT found. Adding DEFAULT.");
+ flagsSet.add(RandomXFlag.DEFAULT);
+ }
+
+ logInfo("Final recommended flags set: " + flagsSet.stream().map(Enum::name).collect(Collectors.joining(", ")));
return flagsSet;
}
-
}
\ No newline at end of file
diff --git a/src/main/java/io/xdag/crypto/randomx/RandomXVM.java b/src/main/java/io/xdag/crypto/randomx/RandomXVM.java
index 32f70a1..c7b74d7 100644
--- a/src/main/java/io/xdag/crypto/randomx/RandomXVM.java
+++ b/src/main/java/io/xdag/crypto/randomx/RandomXVM.java
@@ -26,8 +26,7 @@
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import lombok.Getter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
import java.util.Set;
@@ -35,45 +34,51 @@
* Wrapper class for RandomX virtual machine operations.
* Manages the lifecycle and state of a RandomX VM instance.
*/
-@Getter
+@Slf4j
public class RandomXVM implements AutoCloseable {
- private static final Logger logger = LoggerFactory.getLogger(RandomXVM.class);
-
/**
- * The RandomX flags used to configure this VM
+ * The RandomX flags used to configure this VM.
*/
+ @Getter
private final Set flags;
/**
- * Pointer to the native VM instance
+ * Pointer to the native VM instance.
*/
@Getter
- private final Pointer point;
+ private final Pointer vmPointer;
/**
- * The cache used by this VM
+ * The cache used by this VM.
*/
+ @Getter
private RandomXCache cache;
/**
- * The dataset used by this VM (may be null in light mode)
+ * The dataset used by this VM (may be null in light mode).
*/
+ @Getter
private RandomXDataset dataset;
/**
* Creates a new RandomX VM instance with the specified configuration.
*
- * @param flags Configuration flags for the VM
- * @param cache The cache to use for VM operations
- * @param dataset The dataset to use for VM operations (may be null)
- * @throws IllegalStateException if VM creation fails
+ * @param flags Configuration flags for the VM.
+ * @param cache The cache to use for VM operations.
+ * @param dataset The dataset to use for VM operations (may be null for light mode).
+ * @throws RuntimeException if VM creation fails.
+ * @throws IllegalArgumentException if parameters are invalid.
*/
public RandomXVM(Set flags, RandomXCache cache, RandomXDataset dataset) {
if (flags == null || flags.isEmpty()) {
- throw new IllegalArgumentException("Flags cannot be null or empty");
+ throw new IllegalArgumentException("Flags cannot be null or empty.");
}
- if (cache == null) {
- throw new IllegalArgumentException("Cache cannot be null");
+ if (cache == null || cache.getCachePointer() == null) {
+ throw new IllegalArgumentException("Cache instance or its pointer cannot be null.");
+ }
+ // Dataset can be null (light mode)
+ if (dataset != null && dataset.getDatasetPointer() == null) {
+ throw new IllegalArgumentException("If a dataset is provided, its pointer cannot be null.");
}
this.flags = flags;
@@ -81,154 +86,216 @@ public RandomXVM(Set flags, RandomXCache cache, RandomXDataset data
this.dataset = dataset;
int flagsValue = RandomXFlag.toValue(flags);
- Pointer datasetPointer = dataset != null ? dataset.getPointer() : null;
+ Pointer cachePtr = cache.getCachePointer();
+ Pointer datasetPtr = (dataset != null) ? dataset.getDatasetPointer() : null;
+
+ log.debug("Preparing to create RandomX VM. Flags: {} ({}), Cache Ptr: {}, Dataset Ptr: {}",
+ flags, flagsValue, Pointer.nativeValue(cachePtr), (datasetPtr != null ? Pointer.nativeValue(datasetPtr) : "null"));
- this.point = RandomXJNALoader.getInstance().randomx_create_vm(
- flagsValue,
- cache.getCachePointer(),
- datasetPointer
- );
+ this.vmPointer = RandomXNative.randomx_create_vm(flagsValue, cachePtr, datasetPtr);
- if (point == null) {
- throw new IllegalStateException("Failed to create RandomX VM");
+ if (vmPointer == null) {
+ String errorMsg = String.format("Failed to create RandomX VM with flags: %s (%d)", flags, flagsValue);
+ log.error(errorMsg);
+ throw new RuntimeException(errorMsg);
}
- logger.debug("RandomX VM created with flags: {}", flags);
+ log.info("RandomX VM created successfully. Pointer: {}, Flags: {}", Pointer.nativeValue(vmPointer), flags);
}
/**
* Updates the cache used by this VM.
*
- * @param cache The new cache to use
- * @throws IllegalArgumentException if cache is null
+ * @param newCache The new cache to use.
+ * @throws IllegalArgumentException if newCache is null or its pointer is null.
+ * @throws IllegalStateException if the VM pointer is null.
*/
- public void setCache(RandomXCache cache) {
- if (cache == null) {
- throw new IllegalArgumentException("Cache cannot be null");
+ public void setCache(RandomXCache newCache) {
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot set cache.");
}
- RandomXJNALoader.getInstance().randomx_vm_set_cache(point, cache.getCachePointer());
- this.cache = cache;
- logger.debug("VM cache updated");
+ if (newCache == null || newCache.getCachePointer() == null) {
+ throw new IllegalArgumentException("New cache instance or its pointer cannot be null.");
+ }
+ RandomXNative.randomx_vm_set_cache(vmPointer, newCache.getCachePointer());
+ this.cache = newCache;
+ log.debug("VM cache updated. New Cache Ptr: {}", Pointer.nativeValue(newCache.getCachePointer()));
}
/**
* Updates the dataset used by this VM.
*
- * @param dataset The new dataset to use (may be null in light mode)
+ * @param newDataset The new dataset to use (can be null for light mode).
+ * @throws IllegalArgumentException if newDataset is not null but its pointer is null.
+ * @throws IllegalStateException if the VM pointer is null.
*/
- public void setDataset(RandomXDataset dataset) {
- Pointer datasetPointer = dataset != null ? dataset.getPointer() : null;
- RandomXJNALoader.getInstance().randomx_vm_set_dataset(point, datasetPointer);
- this.dataset = dataset;
+ public void setDataset(RandomXDataset newDataset) {
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot set dataset.");
+ }
+ // If newDataset is not null, its pointer also cannot be null
+ if (newDataset != null && newDataset.getDatasetPointer() == null) {
+ throw new IllegalArgumentException("If a new dataset is provided, its pointer cannot be null.");
+ }
+ Pointer datasetPtr = (newDataset != null) ? newDataset.getDatasetPointer() : null;
+ RandomXNative.randomx_vm_set_dataset(vmPointer, datasetPtr);
+ this.dataset = newDataset;
+ log.debug("VM dataset updated. New Dataset Ptr: {}", (datasetPtr != null ? Pointer.nativeValue(datasetPtr) : "null"));
}
/**
* Calculates a RandomX hash using the current VM configuration.
*
- * @param input The input data to be hashed
- * @return A 32-byte array containing the calculated hash
- * @throws IllegalArgumentException if input is null
+ * @param input The input data to be hashed.
+ * @return A 32-byte array containing the calculated hash.
+ * @throws IllegalArgumentException if input is null.
+ * @throws IllegalStateException if the VM pointer is null.
*/
public byte[] calculateHash(byte[] input) {
- if (input == null) {
- throw new IllegalArgumentException("Input cannot be null");
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot calculate hash.");
}
- byte[] output = new byte[32];
-
- try (Memory inputMem = new Memory(input.length);
- Memory outputMem = new Memory(32)) {
-
- inputMem.write(0, input, 0, input.length);
- RandomXJNALoader.getInstance().randomx_calculate_hash(point, inputMem, input.length, outputMem);
- outputMem.read(0, output, 0, output.length);
+ if (input == null) {
+ throw new IllegalArgumentException("Input cannot be null.");
}
+ byte[] output = new byte[32]; // RandomX hash is always 32 bytes
- return output;
+ // JNA Memory objects automatically manage native memory allocation and deallocation
+ // (at the end of their scope or during GC).
+ Memory inputMem = new Memory(input.length > 0 ? input.length : 1); // JNA Memory does not accept size 0
+ Memory outputMem = new Memory(output.length);
+ if (input.length > 0) {
+ inputMem.write(0, input, 0, input.length);
+ }
+ RandomXNative.randomx_calculate_hash(vmPointer, inputMem, input.length, outputMem);
+ outputMem.read(0, output, 0, output.length);
+ return output;
}
/**
* Begins a multi-part hash calculation.
*
- * @param input The input data
- * @throws IllegalArgumentException if input is null
+ * @param input The input data.
+ * @throws IllegalArgumentException if input is null.
+ * @throws IllegalStateException if the VM pointer is null.
*/
public void calculateHashFirst(byte[] input) {
- if (input == null) {
- throw new IllegalArgumentException("Input cannot be null");
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot start multi-part hash.");
}
-
- try (Memory inputMem = new Memory(input.length)) {
- inputMem.write(0, input, 0, input.length);
- RandomXJNALoader.getInstance().randomx_calculate_hash_first(point, inputMem, input.length);
+ if (input == null) {
+ throw new IllegalArgumentException("Input cannot be null.");
}
+ Memory inputMem = new Memory(input.length > 0 ? input.length : 1);
+ if (input.length > 0) {
+ inputMem.write(0, input, 0, input.length);
+ }
+ RandomXNative.randomx_calculate_hash_first(vmPointer, inputMem, input.length);
}
/**
* Continues a multi-part hash calculation.
*
- * @param input The input data
- * @return A 32-byte array containing the intermediate hash result
- * @throws IllegalArgumentException if input is null
+ * @param input The input data.
+ * @return A 32-byte array containing the intermediate hash result.
+ * @throws IllegalArgumentException if input is null.
+ * @throws IllegalStateException if the VM pointer is null.
*/
public byte[] calculateHashNext(byte[] input) {
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot continue multi-part hash.");
+ }
if (input == null) {
- throw new IllegalArgumentException("Input cannot be null");
+ throw new IllegalArgumentException("Input cannot be null.");
}
byte[] output = new byte[32];
-
- try (Memory inputMem = new Memory(input.length);
- Memory outputMem = new Memory(32)) {
-
- inputMem.write(0, input, 0, input.length);
- RandomXJNALoader.getInstance().randomx_calculate_hash_next(point, inputMem, input.length, outputMem);
- outputMem.read(0, output, 0, output.length);
- }
- return output;
+ Memory inputMem = new Memory(input.length > 0 ? input.length : 1);
+ Memory outputMem = new Memory(output.length);
+ if (input.length > 0) {
+ inputMem.write(0, input, 0, input.length);
+ }
+ RandomXNative.randomx_calculate_hash_next(vmPointer, inputMem, input.length, outputMem);
+ outputMem.read(0, output, 0, output.length);
+ return output;
}
/**
* Finalizes a multi-part hash calculation.
*
- * @return A 32-byte array containing the final hash result
+ * @return A 32-byte array containing the final hash result.
+ * @throws IllegalStateException if the VM pointer is null.
*/
public byte[] calculateHashLast() {
- byte[] output = new byte[32];
-
- try (Memory outputMem = new Memory(32)) {
- RandomXJNALoader.getInstance().randomx_calculate_hash_last(point, outputMem);
- outputMem.read(0, output, 0, output.length);
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot finalize multi-part hash.");
}
- return output;
+ byte[] output = new byte[32];
+ Memory outputMem = new Memory(output.length);
+ RandomXNative.randomx_calculate_hash_last(vmPointer, outputMem);
+ outputMem.read(0, output, 0, output.length);
+ return output;
}
/**
* Calculates a commitment hash for the given input data.
+ * Note: The implementation of this method is based on observation of the original code.
+ * It first calculates a regular hash, then uses that hash as a seed to calculate the commitment.
+ * The exact behavior and signature of the {@code randomx_calculate_commitment} C API should be confirmed.
*
- * @param input The input data to calculate commitment for
- * @return A 32-byte array containing the calculated commitment hash
+ * @param originalInput The original input data that was hashed.
+ * @param preCalculatedHash The hash previously calculated from originalInput.
+ * @return A 32-byte array containing the calculated commitment.
+ * @throws IllegalArgumentException if originalInput or preCalculatedHash is null, or if preCalculatedHash is not 32 bytes.
+ * @throws IllegalStateException if the VM pointer is null.
*/
- public byte[] calcStringCommitment(byte[] input) {
- Pointer inputPointer = new Memory(input.length);
- inputPointer.write(0, input, 0, input.length);
+ public byte[] calculateCommitment(byte[] originalInput, byte[] preCalculatedHash) {
+ if (vmPointer == null) {
+ throw new IllegalStateException("VM pointer is null, cannot calculate commitment.");
+ }
+ if (originalInput == null) {
+ throw new IllegalArgumentException("Original input cannot be null.");
+ }
+ if (preCalculatedHash == null || preCalculatedHash.length != RandomXUtils.RANDOMX_HASH_SIZE) {
+ throw new IllegalArgumentException("Pre-calculated hash cannot be null and must be " + RandomXUtils.RANDOMX_HASH_SIZE + " bytes long.");
+ }
- byte[] output = new byte[32];
- Pointer outputPointer = new Memory(output.length);
- RandomXJNALoader.getInstance().randomx_calculate_hash(point, inputPointer, input.length, outputPointer);
- outputPointer.read(0, output, 0, output.length);
- RandomXJNALoader.getInstance().randomx_calculate_commitment(inputPointer, input.length, outputPointer, outputPointer);
- outputPointer.read(0, output, 0, output.length);
- return output;
+ byte[] commitmentOutput = new byte[RandomXUtils.RANDOMX_HASH_SIZE];
+ Memory originalInputMem = new Memory(originalInput.length > 0 ? originalInput.length : 1); // JNA requires non-zero size for empty inputs
+ Memory preCalculatedHashMem = new Memory(preCalculatedHash.length);
+ Memory commitmentOutputMem = new Memory(commitmentOutput.length);
+
+ if (originalInput.length > 0) {
+ originalInputMem.write(0, originalInput, 0, originalInput.length);
+ }
+ // preCalculatedHash is guaranteed to be non-null and 32 bytes here
+ preCalculatedHashMem.write(0, preCalculatedHash, 0, preCalculatedHash.length);
+
+ // Call the native method with parameters matching the C API
+ // (Pointer input, long inputSize, Pointer hash_in, Pointer com_out)
+ RandomXNative.randomx_calculate_commitment(originalInputMem, originalInput.length, preCalculatedHashMem, commitmentOutputMem);
+ commitmentOutputMem.read(0, commitmentOutput, 0, commitmentOutput.length);
+ return commitmentOutput;
}
/**
- * Releases the native VM resources.
+ * Releases native VM resources.
* This method is idempotent and can be called multiple times safely.
*/
@Override
public void close() {
- if (point != null) {
- RandomXJNALoader.getInstance().randomx_destroy_vm(point);
- logger.debug("RandomX VM destroyed");
+ if (vmPointer != null) {
+ // Check if vmPointer is still valid to prevent operations on an already destroyed VM.
+ // While JNA's destroy is generally safe, this is an extra layer of protection.
+ try {
+ RandomXNative.randomx_destroy_vm(vmPointer);
+ log.info("RandomX VM destroyed. Pointer: {}", Pointer.nativeValue(vmPointer));
+ } catch (Throwable t) {
+ // Generally, destroy should not throw an error, but just in case.
+ log.error("Error occurred while destroying RandomX VM. Pointer: {}", Pointer.nativeValue(vmPointer), t);
+ }
+ // Do not set vmPointer to null as it is final. The object itself will no longer be usable.
+ } else {
+ log.debug("Attempting to destroy RandomX VM, but pointer is already null (possibly already destroyed or never created).");
}
}
}
\ No newline at end of file
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXBenchmark.java b/src/test/java/io/xdag/crypto/randomx/RandomXBenchmark.java
index b889fe0..95844c8 100644
--- a/src/test/java/io/xdag/crypto/randomx/RandomXBenchmark.java
+++ b/src/test/java/io/xdag/crypto/randomx/RandomXBenchmark.java
@@ -50,6 +50,9 @@ public class RandomXBenchmark {
private static final Logger logger = LoggerFactory.getLogger(RandomXBenchmark.class);
+ @Param({"NO_JIT", "JIT"}) // Parameter for controlling JIT flag
+ public String compilationMode;
+
// Sample block template for hash calculation
private static final byte[] BLOCK_TEMPLATE = {
(byte)0x07, (byte)0x07, (byte)0xf7, (byte)0xa4, (byte)0xf0, (byte)0xd6, (byte)0x05, (byte)0xb3,
@@ -109,15 +112,56 @@ public void tearDown() {
*/
@Setup(Level.Trial)
public void setup() {
- logger.info("Setting up shared resources");
- flags = RandomXUtils.getFlagsSet();
+ logger.info("Setting up shared resources for RandomXBenchmark");
+
+ Set baseFlags = RandomXUtils.getRecommendedFlags(); // Get base recommended flags
+
+ logger.info("Base recommended flags: {}", baseFlags);
+
+ String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
+ boolean isMac = osName.contains("mac");
+
+ if ("JIT".equals(compilationMode)) {
+ logger.info("Compilation Mode: JIT selected. Enabling JIT flag.");
+ baseFlags.add(RandomXFlag.JIT);
+
+ if (isMac) {
+ logger.info("On macOS, ensuring SECURE flag is also enabled when JIT is selected, as per user observation.");
+ baseFlags.add(RandomXFlag.SECURE); // Ensure SECURE is present for JIT on macOS
+ } else {
+ // For non-macOS systems, if SECURE is present and potentially conflicts with JIT,
+ // it might be safer to remove SECURE when JIT is prioritized.
+ if (baseFlags.contains(RandomXFlag.SECURE)) {
+ logger.warn("JIT and SECURE flags may be incompatible on non-macOS. Removing SECURE flag as JIT is prioritized for this mode.");
+ baseFlags.remove(RandomXFlag.SECURE);
+ }
+ }
+ } else { // NO_JIT or any other value
+ logger.info("Compilation Mode: NO_JIT selected. Ensuring JIT flag is disabled.");
+ baseFlags.remove(RandomXFlag.JIT);
+ // If not using JIT, SECURE flag (if recommended by getRecommendedFlags()) should typically be kept.
+ // No specific action needed here for SECURE if getRecommendedFlags() already handles it well for NO_JIT.
+ }
+
+ this.flags = EnumSet.copyOf(baseFlags); // Use a defensive copy
+ logger.info("Benchmark (compilationMode={}) will use final flags for VMs and Cache: {}", compilationMode, this.flags);
- cache = new RandomXCache(flags);
- cache.init(BLOCK_TEMPLATE);
+ cache = new RandomXCache(this.flags);
+ cache.init(BLOCK_TEMPLATE);
+ logger.info("Shared RandomXCache initialized with BLOCK_TEMPLATE using flags: {}", this.flags);
- dataset = new RandomXDataset(flags);
+ Set datasetAllocFlags = EnumSet.noneOf(RandomXFlag.class);
+ datasetAllocFlags.add(RandomXFlag.FULL_MEM);
+
+ if (this.flags.contains(RandomXFlag.LARGE_PAGES)) {
+ datasetAllocFlags.add(RandomXFlag.LARGE_PAGES);
+ logger.info("LARGE_PAGES flag detected in VM/Cache flags, adding it to dataset allocation flags.");
+ }
+
+ dataset = new RandomXDataset(datasetAllocFlags);
+ logger.info("Shared RandomXDataset allocated with specific dataset allocation flags: {}. It will be initialized by ThreadState if mining mode is used.", datasetAllocFlags);
- logger.info("Shared resources setup completed");
+ logger.info("Shared resources setup completed for RandomXBenchmark.");
}
/**
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXCacheTest.java b/src/test/java/io/xdag/crypto/randomx/RandomXCacheTest.java
index d810875..eb66285 100644
--- a/src/test/java/io/xdag/crypto/randomx/RandomXCacheTest.java
+++ b/src/test/java/io/xdag/crypto/randomx/RandomXCacheTest.java
@@ -26,7 +26,6 @@
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
-import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
@@ -36,7 +35,6 @@
*/
public class RandomXCacheTest {
- private final Set flagsSet = RandomXUtils.getFlagsSet();
private final byte[] keyBytes = "test_key".getBytes(StandardCharsets.UTF_8);
/**
@@ -45,7 +43,7 @@ public class RandomXCacheTest {
*/
@Test
public void testAllocAndRelease() {
- try (RandomXCache cache = new RandomXCache(flagsSet)) {
+ try (RandomXCache cache = new RandomXCache(RandomXUtils.getRecommendedFlags())) {
assertNotNull(cache.getCachePointer(), "Cache pointer should not be null.");
} // Cache is automatically released here.
}
@@ -56,7 +54,7 @@ public void testAllocAndRelease() {
*/
@Test
public void testInit() {
- try (RandomXCache cache = new RandomXCache(flagsSet)) {
+ try (RandomXCache cache = new RandomXCache(RandomXUtils.getRecommendedFlags())) {
assertNotNull(cache.getCachePointer(), "Cache pointer should not be null.");
cache.init(keyBytes);
} // Cache is automatically released here.
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXDatasetTest.java b/src/test/java/io/xdag/crypto/randomx/RandomXDatasetTest.java
index 69eb8fc..62836b7 100644
--- a/src/test/java/io/xdag/crypto/randomx/RandomXDatasetTest.java
+++ b/src/test/java/io/xdag/crypto/randomx/RandomXDatasetTest.java
@@ -23,6 +23,8 @@
*/
package io.xdag.crypto.randomx;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
@@ -31,45 +33,104 @@
import static org.junit.jupiter.api.Assertions.*;
/**
- * Unit tests for RandomXDataset class.
- * Tests the allocation, initialization and release of RandomX dataset resources.
- * Includes tests for multi-threaded initialization based on CPU cores.
+ * Unit tests for the RandomXDataset class.
*/
public class RandomXDatasetTest {
- private final Set flagsSet = RandomXUtils.getFlagsSet();
- private final byte[] keyBytes = "test_key".getBytes(StandardCharsets.UTF_8);
+ // Member variables initialized in setUp()
+ private Set flags;
+ private RandomXCache cache;
+ private RandomXDataset dataset;
/**
- * Tests the allocation and automatic release of RandomX dataset resources.
- * Verifies that the dataset pointer is properly initialized.
+ * Sets up the necessary components before each test.
+ * Allocates and initializes a cache, and allocates a dataset.
*/
- @Test
- public void testAllocAndRelease() {
- try (RandomXDataset dataset = new RandomXDataset(flagsSet)) {
- assertNotNull(dataset.getPointer(), "Dataset pointer should not be null.");
+ @BeforeEach
+ void setUp() {
+ // Get recommended flags
+ flags = RandomXUtils.getRecommendedFlags(); // Use refactored method
+ assertNotNull(flags, "Flags should not be null");
+
+ // Initialize cache, as dataset initialization depends on it
+ cache = new RandomXCache(flags);
+ // Use a specific key for dataset tests
+ cache.init("test_key_for_dataset".getBytes(StandardCharsets.UTF_8));
+ assertNotNull(cache.getCachePointer(), "Cache pointer should not be null in setUp");
+
+ // Allocate dataset
+ dataset = new RandomXDataset(flags);
+ assertNotNull(dataset.getDatasetPointer(), "Dataset pointer should not be null after allocation in setUp"); // Use getDatasetPointer()
+ }
+
+ /**
+ * Cleans up resources after each test.
+ * Releases the dataset and cache.
+ */
+ @AfterEach
+ void tearDown() {
+ if (dataset != null) {
+ dataset.close();
+ dataset = null;
+ }
+ if (cache != null) {
+ cache.close();
+ cache = null;
}
}
/**
- * Tests the initialization of RandomX dataset with a cache.
- * Measures and logs the initialization time.
- * Verifies that initialization completes successfully and dataset pointer is valid.
+ * Tests if the dataset is allocated correctly in setUp and can be released.
+ * Allocation happens in setUp, release happens in tearDown.
*/
@Test
- void testInitialization() {
- try (RandomXCache cache = new RandomXCache(flagsSet);
- RandomXDataset dataset = new RandomXDataset(flagsSet)) {
-
- cache.init(keyBytes);
- long startTime = System.currentTimeMillis();
-
- assertDoesNotThrow(() -> dataset.init(cache));
-
- long elapsedTime = System.currentTimeMillis() - startTime;
- System.out.println("Dataset initialized in " + elapsedTime + " ms.");
- assertNotNull(dataset.getPointer(), "Dataset pointer should not be null.");
- }
+ void testDatasetAllocationAndRelease() {
+ // Allocation is done in setUp, test existence and pointer validity here
+ assertNotNull(dataset, "Dataset should have been allocated in setUp");
+ assertNotNull(dataset.getDatasetPointer(), "Dataset pointer should not be null after allocation"); // Use getDatasetPointer()
+ }
+
+ /**
+ * Tests the initialization of the dataset.
+ * Assumes the dataset is allocated and cache is initialized in setUp.
+ */
+ @Test
+ void testDatasetInit() {
+ assertNotNull(cache, "Cache should be initialized in setUp");
+ assertNotNull(dataset, "Dataset should be allocated in setUp");
+ assertNotNull(dataset.getDatasetPointer(), "Dataset pointer should not be null before init"); // Use getDatasetPointer()
+
+ // Attempt to initialize the dataset using the initialized cache
+ assertDoesNotThrow(() -> dataset.init(cache), "Dataset initialization should not throw an exception");
}
+ /**
+ * Tests that initializing the dataset with a null cache throws an exception.
+ */
+ @Test
+ void testDatasetInitWithNullCache() {
+ assertNotNull(dataset, "Dataset should be allocated in setUp");
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ dataset.init(null); // Pass null cache
+ });
+ assertTrue(exception.getMessage().contains("Valid cache instance"), "Exception message should indicate invalid cache");
+ }
+
+ /**
+ * Tests allocating a dataset with null flags throws an exception.
+ */
+ @Test
+ void testAllocateWithNullFlags() {
+ assertThrows(IllegalArgumentException.class, () -> new RandomXDataset(null));
+ }
+
+ /**
+ * Tests allocating a dataset with empty flags throws an exception.
+ */
+ @Test
+ void testAllocateWithEmptyFlags() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ new RandomXDataset(java.util.EnumSet.noneOf(RandomXFlag.class));
+ });
+ }
}
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXJNALoaderTest.java b/src/test/java/io/xdag/crypto/randomx/RandomXJNALoaderTest.java
deleted file mode 100644
index 509a52d..0000000
--- a/src/test/java/io/xdag/crypto/randomx/RandomXJNALoaderTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2022-2030 The XdagJ Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package io.xdag.crypto.randomx;
-
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Unit tests for RandomXJNALoader class.
- * Tests the loading and initialization of the RandomX native library through JNA.
- */
-public class RandomXJNALoaderTest {
-
- /**
- * Tests that the RandomX native library is properly loaded and initialized.
- * Verifies that the singleton instance is not null after loading.
- */
- @Test
- public void testLibraryLoading() {
- assertNotNull(RandomXJNALoader.getInstance(), "RandomXJNA instance should not be null.");
- }
-
-}
\ No newline at end of file
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXTemplateTest.java b/src/test/java/io/xdag/crypto/randomx/RandomXTemplateTest.java
index 01d825c..520267f 100644
--- a/src/test/java/io/xdag/crypto/randomx/RandomXTemplateTest.java
+++ b/src/test/java/io/xdag/crypto/randomx/RandomXTemplateTest.java
@@ -32,10 +32,7 @@
import static org.junit.jupiter.api.Assertions.*;
/**
- * Unit tests for RandomXTemplate class.
- * Tests the hash calculation and key change functionality.
- * Verifies that the RandomX hash calculations produce expected results
- * and that key changes are handled correctly.
+ * Unit tests for the RandomXTemplate class.
*/
public class RandomXTemplateTest {
@@ -51,17 +48,17 @@ public void testCalculateHash() {
byte[] key3Bytes = key3.getBytes(StandardCharsets.UTF_8);
byte[] key4Bytes = key4.getBytes(StandardCharsets.UTF_8);
- Set flagSet = RandomXUtils.getFlagsSet();
+ Set flagSet = RandomXUtils.getRecommendedFlags();
RandomXCache cache = new RandomXCache(flagSet);
cache.init(key1Bytes);
HexFormat hex = HexFormat.of();
RandomXTemplate template = RandomXTemplate.builder()
- .cache(cache)
- .miningMode(false)
- .flags(flagSet)
- .build();
+ .cache(cache)
+ .miningMode(false)
+ .flags(flagSet)
+ .build();
template.init();
byte[] hash = template.calculateHash(key2Bytes);
assertEquals("781315d3e78dc16a5060cb87677ca548d8b9aabdef5221a2851b2cc72aa2875b", hex.formatHex(hash));
@@ -69,10 +66,10 @@ public void testCalculateHash() {
cache = new RandomXCache(flagSet);
cache.init(key3Bytes);
template = RandomXTemplate.builder()
- .cache(cache)
- .miningMode(false)
- .flags(flagSet)
- .build();
+ .cache(cache)
+ .miningMode(false)
+ .flags(flagSet)
+ .build();
template.init();
hash = template.calculateHash(key3Bytes);
assertEquals("33e17472f3f691252d1f28a2e945b990c5878f514034006df5a06a23dc1cada0", hex.formatHex(hash));
@@ -80,10 +77,10 @@ public void testCalculateHash() {
cache = new RandomXCache(flagSet);
cache.init(key4Bytes);
template = RandomXTemplate.builder()
- .cache(cache)
- .miningMode(false)
- .flags(flagSet)
- .build();
+ .cache(cache)
+ .miningMode(false)
+ .flags(flagSet)
+ .build();
template.init();
hash = template.calculateHash(key4Bytes);
assertEquals("5d4155322b69284bf45fa8ac182384490a87c55a6af47b7e72558cafa8832bd9", hex.formatHex(hash));
@@ -97,17 +94,17 @@ public void testChangeKey() {
String key2 = "world xdagj-native-randomx";
byte[] key2Bytes = key2.getBytes(StandardCharsets.UTF_8);
- Set flagSet = RandomXUtils.getFlagsSet();
+ Set flagSet = RandomXUtils.getRecommendedFlags();
RandomXCache cache = new RandomXCache(flagSet);
cache.init(key1Bytes);
HexFormat hex = HexFormat.of();
RandomXTemplate template = RandomXTemplate.builder()
- .cache(cache)
- .miningMode(false)
- .flags(flagSet)
- .build();
+ .cache(cache)
+ .miningMode(false)
+ .flags(flagSet)
+ .build();
template.init();
byte[] hash = template.calculateHash(key1Bytes);
assertEquals("5d4155322b69284bf45fa8ac182384490a87c55a6af47b7e72558cafa8832bd9", hex.formatHex(hash));
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXTests.java b/src/test/java/io/xdag/crypto/randomx/RandomXTests.java
index 921d8a8..aeb7163 100644
--- a/src/test/java/io/xdag/crypto/randomx/RandomXTests.java
+++ b/src/test/java/io/xdag/crypto/randomx/RandomXTests.java
@@ -23,6 +23,7 @@
*/
package io.xdag.crypto.randomx;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -31,8 +32,10 @@
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import java.util.Set;
+import java.util.EnumSet;
+import java.util.Arrays;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for RandomX hash calculation functionality.
@@ -41,26 +44,52 @@
public class RandomXTests {
private RandomXTemplate template;
private HexFormat hex;
+ private RandomXCache cache;
+ private RandomXDataset dataset;
/**
* Sets up the test environment before each test.
- * Initializes RandomX template with cache and dataset.
+ * Initializes RandomX template for light mode operations.
*/
@BeforeEach
public void setUp() {
- Set flagsSet = RandomXUtils.getFlagsSet();
- RandomXCache cache = new RandomXCache(flagsSet);
- RandomXDataset dataset = new RandomXDataset(flagsSet);
+ Set flags = RandomXUtils.getRecommendedFlags();
+ assertNotNull(flags, "Flags should not be null in setUp");
+
+ // For these tests, which are light-mode (non-mining), a dataset is not strictly necessary
+ // for the template's operation. We will initialize the cache only.
+ cache = new RandomXCache(flags);
+ // this.dataset = new RandomXDataset(flags); // Not creating dataset for light mode tests
+ this.dataset = null; // Explicitly null
+
template = RandomXTemplate.builder()
.cache(cache)
- .dataset(dataset)
+ .dataset(null) // Explicitly pass null for dataset in light mode
.miningMode(false)
- .flags(flagsSet)
+ .flags(flags)
.build();
template.init();
hex = HexFormat.of();
}
+ @AfterEach
+ public void tearDown() {
+ if (template != null) {
+ template.close();
+ template = null;
+ }
+ if (cache != null) {
+ cache.close();
+ cache = null;
+ }
+ // Since this.dataset is now explicitly set to null or not created in setUp for these tests,
+ // this check will correctly skip closing a null dataset.
+ if (dataset != null) {
+ dataset.close();
+ dataset = null;
+ }
+ }
+
/**
* Tests string hash calculation with different key-input pairs.
* Verifies that the hash output matches expected values.
@@ -93,7 +122,7 @@ void testCalcStringCommitment(String key, String input, String output) {
byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
template.changeKey(keyBytes);
- assertEquals(output, hex.formatHex(template.calcStringCommitment(inputBytes)));
+ assertEquals(output, hex.formatHex(template.calculateCommitment(inputBytes)));
}
/**
diff --git a/src/test/java/io/xdag/crypto/randomx/RandomXVMTest.java b/src/test/java/io/xdag/crypto/randomx/RandomXVMTest.java
index f15ed6e..fbfff9d 100644
--- a/src/test/java/io/xdag/crypto/randomx/RandomXVMTest.java
+++ b/src/test/java/io/xdag/crypto/randomx/RandomXVMTest.java
@@ -24,41 +24,217 @@
package io.xdag.crypto.randomx;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Disabled;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
/**
- * Unit tests for RandomXVM class.
- * Tests the hash calculation functionality using RandomX virtual machine.
- * Verifies the basic operations and output format of the VM implementation.
+ * Unit tests for the RandomXVM class.
+ * Focuses on VM creation, state changes (cache/dataset), and hash calculations.
*/
public class RandomXVMTest {
/**
- * Tests hash calculation using RandomXVM.
- * This test verifies:
- * 1. VM initialization with given flags and cache
- * 2. Hash calculation with test input
- * 3. Output validation:
- * - Not null check
- * - Correct length (32 bytes) check
- * 4. Proper resource cleanup using try-with-resources
+ * Tests the creation of a RandomX VM in light mode (no dataset).
+ * Verifies that the VM pointer is not null after creation.
*/
@Test
- public void testVMHashCalculation() {
- Set flags = RandomXUtils.getFlagsSet();
- byte[] keyBytes = "test_key".getBytes();
- byte[] input = "test_input".getBytes();
-
- try (RandomXCache cache = new RandomXCache(flags);
- RandomXVM vm = new RandomXVM(flags, cache, null)) {
- cache.init(keyBytes);
- byte[] output = vm.calculateHash(input);
- assertNotNull(output, "Output should not be null.");
- assertEquals(32, output.length, "Output size should be 32 bytes.");
+ void testVMCreationLightMode() {
+ Set testFlags = RandomXUtils.getRecommendedFlags();
+ // Ensure FULL_MEM is not present for a true light mode test,
+ // though getRecommendedFlags might include it.
+ // RandomXVM itself doesn't add/remove FULL_MEM; RandomXTemplate does.
+ // For this test, we explicitly remove it if present to test light VM creation.
+ testFlags.remove(RandomXFlag.FULL_MEM);
+
+ try (RandomXCache localCache = new RandomXCache(testFlags)) {
+ localCache.init("test_key_vm_light".getBytes(StandardCharsets.UTF_8));
+ assertNotNull(localCache.getCachePointer(), "Cache pointer should not be null for VM creation.");
+
+ try (RandomXVM vm = new RandomXVM(testFlags, localCache, null /* No dataset for light mode */)) {
+ assertNotNull(vm.getVmPointer(), "VM pointer should not be null in light mode.");
+ assertEquals(testFlags, vm.getFlags(), "VM flags should match those used for creation.");
+ assertSame(localCache, vm.getCache(), "VM should hold the provided cache instance.");
+ assertNull(vm.getDataset(), "Dataset should be null in light mode.");
+ }
+ }
+ }
+
+ /**
+ * Tests the creation of a RandomX VM in full mode (with a dataset).
+ * Verifies that the VM pointer is not null and dataset is correctly associated.
+ */
+ @Test
+ void testVMCreationFullMode() {
+ Set recommendedFlags = RandomXUtils.getRecommendedFlags();
+
+ // For full mode, a VM operates with a dataset.
+ // The FULL_MEM flag is primarily for dataset allocation/initialization.
+ // We ensure flags used for creating cache, dataset, and VM are consistent for this mode.
+ Set fullModeFlags = EnumSet.copyOf(recommendedFlags);
+ fullModeFlags.add(RandomXFlag.FULL_MEM); // Ensure FULL_MEM is present for full mode context
+
+ try (RandomXCache localCache = new RandomXCache(fullModeFlags);
+ RandomXDataset localDataset = new RandomXDataset(fullModeFlags)) {
+
+ localCache.init("test_key_vm_full".getBytes(StandardCharsets.UTF_8));
+ assertNotNull(localCache.getCachePointer(), "Cache pointer should not be null.");
+
+ localDataset.init(localCache); // Dataset needs initialized cache
+ assertNotNull(localDataset.getDatasetPointer(), "Dataset pointer should not be null.");
+
+ try (RandomXVM vm = new RandomXVM(fullModeFlags, localCache, localDataset)) {
+ assertNotNull(vm.getVmPointer(), "VM pointer should not be null in full mode.");
+ assertEquals(fullModeFlags, vm.getFlags(), "VM flags should match.");
+ assertSame(localCache, vm.getCache(), "VM should use the provided cache.");
+ assertSame(localDataset, vm.getDataset(), "VM should use the provided dataset.");
+ }
+ }
+ }
+
+ /**
+ * Tests hash calculation using the RandomX VM.
+ * Ensures that a hash can be calculated and is of the correct length.
+ */
+ @Test
+ void testVMHashCalculation() {
+ Set testFlags = RandomXUtils.getRecommendedFlags();
+ testFlags.remove(RandomXFlag.FULL_MEM); // Typically light mode for simple hash tests
+
+ byte[] keyBytes = "test_key_hash_calc".getBytes(StandardCharsets.UTF_8);
+ byte[] input = "test_input_for_hash".getBytes(StandardCharsets.UTF_8);
+
+ try (RandomXCache localCache = new RandomXCache(testFlags)) {
+ localCache.init(keyBytes);
+ try (RandomXVM vm = new RandomXVM(testFlags, localCache, null)) {
+ byte[] hash = vm.calculateHash(input);
+ assertNotNull(hash, "Calculated hash should not be null.");
+ assertEquals(32, hash.length, "Hash should be 32 bytes long.");
+ }
}
}
+ /**
+ * Tests changing the cache in an existing VM.
+ */
+ @Test
+ void testVMSetCache() {
+ Set initialFlags = RandomXUtils.getRecommendedFlags();
+ initialFlags.remove(RandomXFlag.FULL_MEM);
+
+ try (RandomXCache cache1 = new RandomXCache(initialFlags);
+ RandomXCache cache2 = new RandomXCache(initialFlags)) {
+
+ cache1.init("key_for_cache1".getBytes(StandardCharsets.UTF_8));
+ cache2.init("key_for_cache2".getBytes(StandardCharsets.UTF_8));
+
+ try (RandomXVM vm = new RandomXVM(initialFlags, cache1, null)) {
+ assertSame(cache1, vm.getCache(), "Initial cache should be cache1.");
+ byte[] hash1 = vm.calculateHash("input".getBytes(StandardCharsets.UTF_8));
+
+ vm.setCache(cache2); // Change the cache
+ assertSame(cache2, vm.getCache(), "Updated cache should be cache2.");
+ byte[] hash2 = vm.calculateHash("input".getBytes(StandardCharsets.UTF_8));
+
+ assertNotNull(hash1, "Hash1 should not be null.");
+ assertNotNull(hash2, "Hash2 should not be null.");
+ assertFalse(java.util.Arrays.equals(hash1, hash2),
+ "Hashes from VMs with different caches (different keys) should differ.");
+ }
+ }
+ }
+
+ /**
+ * Tests changing the dataset in an existing VM.
+ * This test assumes the VM is created with flags compatible with dataset usage.
+ */
+ @Test
+ // @Disabled("Dataset changes and interactions can be complex; needs careful C API review for direct VM manipulation if not using RandomXTemplate") // Re-enabling test
+ void testVMSetDataset() {
+ Set fullModeFlags = RandomXUtils.getRecommendedFlags();
+ fullModeFlags.add(RandomXFlag.FULL_MEM); // Ensure FULL_MEM for dataset context
+
+ byte[] inputBytes = "test_input_for_dataset_change".getBytes(StandardCharsets.UTF_8);
+
+ try (RandomXCache cacheForDs1 = new RandomXCache(fullModeFlags);
+ RandomXDataset dataset1 = new RandomXDataset(fullModeFlags);
+ RandomXCache cacheForDs2 = new RandomXCache(fullModeFlags); // Separate cache for dataset2
+ RandomXDataset dataset2 = new RandomXDataset(fullModeFlags)) {
+
+ cacheForDs1.init("key_for_dataset1".getBytes(StandardCharsets.UTF_8));
+ dataset1.init(cacheForDs1);
+ assertNotNull(dataset1.getDatasetPointer(), "Dataset1 pointer should not be null.");
+
+ cacheForDs2.init("key_for_dataset2".getBytes(StandardCharsets.UTF_8)); // Different key for cache2
+ dataset2.init(cacheForDs2);
+ assertNotNull(dataset2.getDatasetPointer(), "Dataset2 pointer should not be null.");
+
+ // VM initially uses dataset1 (via its cache, cacheForDs1)
+ try (RandomXVM vm = new RandomXVM(fullModeFlags, cacheForDs1, dataset1)) {
+ assertSame(dataset1, vm.getDataset(), "Initial dataset should be dataset1.");
+ assertSame(cacheForDs1, vm.getCache(), "Initial cache for VM should be cacheForDs1.");
+
+ byte[] hash1 = vm.calculateHash(inputBytes);
+ assertNotNull(hash1, "Hash1 should not be null.");
+
+ vm.setCache(cacheForDs2); // Change cache first
+ vm.setDataset(dataset2); // Then change dataset
+
+ assertSame(dataset2, vm.getDataset(), "Updated dataset should be dataset2.");
+ assertSame(cacheForDs2, vm.getCache(), "Updated cache for VM should be cacheForDs2.");
+
+ byte[] hash2 = vm.calculateHash(inputBytes);
+ assertNotNull(hash2, "Hash2 should not be null.");
+
+ assertFalse(java.util.Arrays.equals(hash1, hash2),
+ "Hashes from VMs with different datasets (and their associated caches) should differ.");
+ }
+ }
+ }
+
+ /**
+ * Tests creation of VM with null flags (should throw IllegalArgumentException).
+ */
+ @Test
+ void testVMCreationNullFlags() {
+ try (RandomXCache localCache = new RandomXCache(RandomXUtils.getRecommendedFlags())) {
+ localCache.init("key".getBytes(StandardCharsets.UTF_8));
+ assertThrows(IllegalArgumentException.class, () -> {
+ new RandomXVM(null, localCache, null);
+ });
+ }
+ }
+
+ /**
+ * Tests creation of VM with null cache (should throw IllegalArgumentException).
+ */
+ @Test
+ void testVMCreationNullCache() {
+ Set testFlags = RandomXUtils.getRecommendedFlags();
+ assertThrows(IllegalArgumentException.class, () -> {
+ new RandomXVM(testFlags, null, null);
+ });
+ }
+
+ /**
+ * Tests setting a null cache on an existing VM (should throw IllegalArgumentException).
+ */
+ @Test
+ void testVMSetCacheWithNull() {
+ Set testFlags = RandomXUtils.getRecommendedFlags();
+ testFlags.remove(RandomXFlag.FULL_MEM); // Light mode is sufficient
+
+ try (RandomXCache initialCache = new RandomXCache(testFlags)) {
+ initialCache.init("initial_key".getBytes(StandardCharsets.UTF_8));
+ try (RandomXVM vm = new RandomXVM(testFlags, initialCache, null)) {
+ assertThrows(IllegalArgumentException.class, () -> {
+ vm.setCache(null); // Attempt to set null cache
+ }, "Setting a null cache should throw IllegalArgumentException.");
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/resources/simplelogger.properties b/src/test/resources/simplelogger.properties
similarity index 100%
rename from src/main/resources/simplelogger.properties
rename to src/test/resources/simplelogger.properties