randomy_rs/
lib.rs

1// Copyright 2019. The Tari Project
2//
3// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4// following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7// disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10// following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13// products derived from this software without specific prior written permission.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23//! # RandomX
24//!
25//! The `randomx-rs` crate provides bindings to the RandomX proof-of-work (PoW) system.
26//!
27//! From the [RandomX github repo]:
28//!
29//! "RandomX is a proof-of-work (PoW) algorithm that is optimized for general-purpose CPUs. RandomX uses random code
30//! execution together with several memory-hard techniques to minimize the efficiency advantage of specialized
31//! hardware."
32//!
33//! Read more about how RandomX works in the [design document].
34//!
35//! [RandomX github repo]: <https://github.com/tevador/RandomX>
36//! [design document]: <https://github.com/tevador/RandomX/blob/master/doc/design.md>
37mod bindings;
38use std::{convert::TryFrom, num::TryFromIntError, ptr, sync::Arc};
39
40use bindings::{
41    randomx_alloc_cache,
42    randomx_alloc_dataset,
43    randomx_cache,
44    randomx_calculate_hash,
45    randomx_create_vm,
46    randomx_dataset,
47    randomx_dataset_item_count,
48    randomx_destroy_vm,
49    randomx_get_dataset_memory,
50    randomx_init_cache,
51    randomx_init_dataset,
52    randomx_release_cache,
53    randomx_release_dataset,
54    randomx_vm,
55    randomx_vm_set_cache,
56    randomx_vm_set_dataset,
57    RANDOMX_DATASET_ITEM_SIZE,
58    RANDOMX_HASH_SIZE,
59};
60use bitflags::bitflags;
61use libc::{c_ulong, c_void};
62use thiserror::Error;
63
64use crate::bindings::{
65    randomx_calculate_hash_first,
66    randomx_calculate_hash_last,
67    randomx_calculate_hash_next,
68    randomx_get_flags,
69};
70
71bitflags! {
72    /// RandomX Flags are used to configure the library.
73    pub struct RandomXFlag: u32 {
74        /// No flags set. Works on all platforms, but is the slowest.
75        const FLAG_DEFAULT      = 0b0000_0000;
76        /// Allocate memory in large pages.
77        const FLAG_LARGE_PAGES  = 0b0000_0001;
78        /// Use hardware accelerated AES.
79        const FLAG_HARD_AES     = 0b0000_0010;
80        /// Use the full dataset.
81        const FLAG_FULL_MEM     = 0b0000_0100;
82        /// Use JIT compilation support.
83        const FLAG_JIT          = 0b0000_1000;
84        /// When combined with FLAG_JIT, the JIT pages are never writable and executable at the
85        /// same time.
86        const FLAG_SECURE       = 0b0001_0000;
87        /// Optimize Argon2 for CPUs with the SSSE3 instruction set.
88        const FLAG_ARGON2_SSSE3 = 0b0010_0000;
89        /// Optimize Argon2 for CPUs with the AVX2 instruction set.
90        const FLAG_ARGON2_AVX2  = 0b0100_0000;
91        /// Optimize Argon2 for CPUs without the AVX2 or SSSE3 instruction sets.
92        const FLAG_ARGON2       = 0b0110_0000;
93    }
94}
95
96impl RandomXFlag {
97    /// Returns the recommended flags to be used.
98    ///
99    /// Does not include:
100    /// * FLAG_LARGE_PAGES
101    /// * FLAG_FULL_MEM
102    /// * FLAG_SECURE
103    ///
104    /// The above flags need to be set manually, if required.
105    pub fn get_recommended_flags() -> RandomXFlag {
106        RandomXFlag {
107            bits: unsafe { randomx_get_flags() },
108        }
109    }
110}
111
112impl Default for RandomXFlag {
113    /// Default value for RandomXFlag
114    fn default() -> RandomXFlag {
115        RandomXFlag::FLAG_DEFAULT
116    }
117}
118
119#[derive(Debug, Clone, Error)]
120/// This enum specifies the possible errors that may occur.
121pub enum RandomXError {
122    #[error("Problem creating the RandomX object: {0}")]
123    CreationError(String),
124    #[error("Problem with configuration flags: {0}")]
125    FlagConfigError(String),
126    #[error("Problem with parameters supplied: {0}")]
127    ParameterError(String),
128    #[error("Failed to convert Int to usize")]
129    TryFromIntError(#[from] TryFromIntError),
130    #[error("Unknown problem running RandomX: {0}")]
131    Other(String),
132}
133
134#[derive(Debug)]
135struct RandomXCacheInner {
136    cache_ptr: *mut randomx_cache,
137}
138
139impl Drop for RandomXCacheInner {
140    /// De-allocates memory for the `cache` object
141    fn drop(&mut self) {
142        unsafe {
143            randomx_release_cache(self.cache_ptr);
144        }
145    }
146}
147
148#[derive(Debug, Clone)]
149/// The Cache is used for light verification and Dataset construction.
150pub struct RandomXCache {
151    inner: Arc<RandomXCacheInner>,
152}
153
154impl RandomXCache {
155    /// Creates and alllcates memory for a new cache object, and initializes it with
156    /// the key value.
157    ///
158    /// `flags` is any combination of the following two flags:
159    /// * FLAG_LARGE_PAGES
160    /// * FLAG_JIT
161    ///
162    /// and (optionally) one of the following flags (depending on instruction set supported):
163    /// * FLAG_ARGON2_SSSE3
164    /// * FLAG_ARGON2_AVX2
165    ///
166    /// `key` is a sequence of u8 used to initialize SuperScalarHash.
167    pub fn new(flags: RandomXFlag, key: &[u8]) -> Result<RandomXCache, RandomXError> {
168        if key.is_empty() {
169            Err(RandomXError::ParameterError("key is empty".to_string()))
170        } else {
171            let test = unsafe { randomx_alloc_cache(flags.bits) };
172            if test.is_null() {
173                Err(RandomXError::CreationError("Could not allocate cache".to_string()))
174            } else {
175                let inner = RandomXCacheInner { cache_ptr: test };
176                let result = RandomXCache { inner: Arc::new(inner) };
177                let key_ptr = key.as_ptr() as *mut c_void;
178                let key_size = key.len() as usize;
179                unsafe {
180                    randomx_init_cache(result.inner.cache_ptr, key_ptr, key_size);
181                }
182                Ok(result)
183            }
184        }
185    }
186}
187
188#[derive(Debug)]
189struct RandomXDatasetInner {
190    dataset_ptr: *mut randomx_dataset,
191    dataset_count: u32,
192    #[allow(dead_code)]
193    cache: RandomXCache,
194}
195
196impl Drop for RandomXDatasetInner {
197    /// De-allocates memory for the `dataset` object.
198    fn drop(&mut self) {
199        unsafe {
200            randomx_release_dataset(self.dataset_ptr);
201        }
202    }
203}
204
205#[derive(Debug, Clone)]
206/// The Dataset is a read-only memory structure that is used during VM program execution.
207pub struct RandomXDataset {
208    inner: Arc<RandomXDatasetInner>,
209}
210
211impl RandomXDataset {
212    /// Creates a new dataset object, allocates memory to the `dataset` object and initializes it.
213    ///
214    /// `flags` is one of the following:
215    /// * FLAG_DEFAULT
216    /// * FLAG_LARGE_PAGES
217    ///
218    /// `cache` is a cache object.
219    ///
220    /// `start` is the item number where initialization should start, recommended to pass in 0.
221    // Conversions may be lossy on Windows or Linux
222    #[allow(clippy::useless_conversion)]
223    pub fn new(flags: RandomXFlag, cache: RandomXCache, start: u32) -> Result<RandomXDataset, RandomXError> {
224        let count = RANDOMX_DATASET_ITEM_SIZE - 1 - start;
225        let test = unsafe { randomx_alloc_dataset(flags.bits) };
226        if test.is_null() {
227            Err(RandomXError::CreationError("Could not allocate dataset".to_string()))
228        } else {
229            let inner = RandomXDatasetInner {
230                dataset_ptr: test,
231                dataset_count: count,
232                cache,
233            };
234            let result = RandomXDataset { inner: Arc::new(inner) };
235            let item_count = result
236                .count()
237                .map_err(|err| RandomXError::CreationError(format!("Could not get dataset count:{}", err)))?;
238            // Mirror the assert checks inside randomx_init_dataset call
239            if (start < item_count && count <= item_count) || (start + item_count <= count) {
240                unsafe {
241                    randomx_init_dataset(
242                        result.inner.dataset_ptr,
243                        result.inner.cache.inner.cache_ptr,
244                        c_ulong::from(start),
245                        c_ulong::from(count),
246                    );
247                }
248                Ok(result)
249            } else {
250                let reason = format!(
251                    "Dataset `start` or `count` was out of bounds: start: {}, count: {}, actual count: {}",
252                    start, count, item_count
253                );
254                Err(RandomXError::CreationError(reason))
255            }
256        }
257    }
258
259    /// Returns the number of items in the `dataset` or an error on failure.
260    pub fn count(&self) -> Result<u32, RandomXError> {
261        match unsafe { randomx_dataset_item_count() } {
262            0 => Err(RandomXError::Other("Dataset item count was 0".to_string())),
263            x => {
264                // This weirdness brought to you by c_ulong being different on Windows and Linux
265                Ok(u32::try_from(u64::from(x))?)
266            },
267        }
268    }
269
270    /// Returns the values of the internal memory buffer of the `dataset` or an error on failure.
271    pub fn get_data(&self) -> Result<Vec<u8>, RandomXError> {
272        let memory = unsafe { randomx_get_dataset_memory(self.inner.dataset_ptr) };
273        if memory.is_null() {
274            Err(RandomXError::Other("Could not get dataset memory".into()))
275        } else {
276            let count = usize::try_from(self.inner.dataset_count)?;
277            let mut result: Vec<u8> = vec![0u8; count];
278            let n = usize::try_from(self.inner.dataset_count)?;
279            unsafe {
280                libc::memcpy(result.as_mut_ptr() as *mut c_void, memory, n);
281            }
282            Ok(result)
283        }
284    }
285}
286
287#[derive(Debug)]
288/// The RandomX Virtual Machine (VM) is a complex instruction set computer that executes generated programs.
289pub struct RandomXVM {
290    flags: RandomXFlag,
291    vm: *mut randomx_vm,
292    linked_cache: Option<RandomXCache>,
293    linked_dataset: Option<RandomXDataset>,
294}
295
296impl Drop for RandomXVM {
297    /// De-allocates memory for the `VM` object.
298    fn drop(&mut self) {
299        unsafe {
300            randomx_destroy_vm(self.vm);
301        }
302    }
303}
304
305impl RandomXVM {
306    /// Creates a new `VM` and initializes it, error on failure.
307    ///
308    /// `flags` is any combination of the following 5 flags:
309    /// * FLAG_LARGE_PAGES
310    /// * FLAG_HARD_AES
311    /// * FLAG_FULL_MEM
312    /// * FLAG_JIT
313    /// * FLAG_SECURE
314    ///
315    /// Or
316    ///
317    /// * FLAG_DEFAULT
318    ///
319    /// `cache` is a cache object, optional if FLAG_FULL_MEM is set.
320    ///
321    /// `dataset` is a dataset object, optional if FLAG_FULL_MEM is not set.
322    pub fn new(
323        flags: RandomXFlag,
324        cache: Option<RandomXCache>,
325        dataset: Option<RandomXDataset>,
326    ) -> Result<RandomXVM, RandomXError> {
327        let is_full_mem = flags.contains(RandomXFlag::FLAG_FULL_MEM);
328        match (cache, dataset) {
329            (None, None) => Err(RandomXError::CreationError("Failed to allocate VM".to_string())),
330            (None, _) if !is_full_mem => Err(RandomXError::FlagConfigError(
331                "No cache and FLAG_FULL_MEM not set".to_string(),
332            )),
333            (_, None) if is_full_mem => Err(RandomXError::FlagConfigError(
334                "No dataset and FLAG_FULL_MEM set".to_string(),
335            )),
336            (cache, dataset) => {
337                let cache_ptr = cache
338                    .as_ref()
339                    .map(|stash| stash.inner.cache_ptr)
340                    .unwrap_or_else(ptr::null_mut);
341                let dataset_ptr = dataset
342                    .as_ref()
343                    .map(|data| data.inner.dataset_ptr)
344                    .unwrap_or_else(ptr::null_mut);
345                let vm = unsafe { randomx_create_vm(flags.bits, cache_ptr, dataset_ptr) };
346                Ok(RandomXVM {
347                    vm,
348                    flags,
349                    linked_cache: cache,
350                    linked_dataset: dataset,
351                })
352            },
353        }
354    }
355
356    /// Re-initializes the `VM` with a new cache that was initialised without
357    /// RandomXFlag::FLAG_FULL_MEM.
358    pub fn reinit_cache(&mut self, cache: RandomXCache) -> Result<(), RandomXError> {
359        if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
360            Err(RandomXError::FlagConfigError(
361                "Cannot reinit cache with FLAG_FULL_MEM set".to_string(),
362            ))
363        } else {
364            unsafe {
365                randomx_vm_set_cache(self.vm, cache.inner.cache_ptr);
366            }
367            self.linked_cache = Some(cache);
368            Ok(())
369        }
370    }
371
372    /// Re-initializes the `VM` with a new dataset that was initialised with
373    /// RandomXFlag::FLAG_FULL_MEM.
374    pub fn reinit_dataset(&mut self, dataset: RandomXDataset) -> Result<(), RandomXError> {
375        if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) {
376            unsafe {
377                randomx_vm_set_dataset(self.vm, dataset.inner.dataset_ptr);
378            }
379            self.linked_dataset = Some(dataset);
380            Ok(())
381        } else {
382            Err(RandomXError::FlagConfigError(
383                "Cannot reinit dataset without FLAG_FULL_MEM set".to_string(),
384            ))
385        }
386    }
387
388    /// Calculates a RandomX hash value and returns it, error on failure.
389    ///
390    /// `input` is a sequence of u8 to be hashed.
391    pub fn calculate_hash(&self, input: &[u8]) -> Result<Vec<u8>, RandomXError> {
392        if input.is_empty() {
393            Err(RandomXError::ParameterError("input was empty".to_string()))
394        } else {
395            let size_input = input.len() as usize;
396            let input_ptr = input.as_ptr() as *mut c_void;
397            let arr = [0; RANDOMX_HASH_SIZE as usize];
398            let output_ptr = arr.as_ptr() as *mut c_void;
399            unsafe {
400                randomx_calculate_hash(self.vm, input_ptr, size_input, output_ptr);
401            }
402            // if this failed, arr should still be empty
403            if arr == [0; RANDOMX_HASH_SIZE as usize] {
404                Err(RandomXError::Other("RandomX calculated hash was empty".to_string()))
405            } else {
406                let result = arr.to_vec();
407                Ok(result)
408            }
409        }
410    }
411
412    /// Calculates hashes from a set of inputs.
413    ///
414    /// `input` is an array of a sequence of u8 to be hashed.
415    #[allow(clippy::needless_range_loop)] // Range loop is not only for indexing `input`
416    pub fn calculate_hash_set(&self, input: &[&[u8]]) -> Result<Vec<Vec<u8>>, RandomXError> {
417        if input.is_empty() {
418            // Empty set
419            return Err(RandomXError::ParameterError("input was empty".to_string()));
420        }
421
422        let mut result = Vec::new();
423        // For single input
424        if input.len() == 1 {
425            let hash = self.calculate_hash(input[0])?;
426            result.push(hash);
427            return Ok(result);
428        }
429
430        // For multiple inputs
431        let mut output_ptr: *mut c_void = ptr::null_mut();
432        let arr = [0; RANDOMX_HASH_SIZE as usize];
433
434        // Not len() as last iteration assigns final hash
435        let iterations = input.len() + 1;
436        for i in 0..iterations {
437            if i == iterations - 1 {
438                // For last iteration
439                unsafe {
440                    randomx_calculate_hash_last(self.vm, output_ptr);
441                }
442            } else {
443                if input[i].is_empty() {
444                    // Stop calculations
445                    if arr != [0; RANDOMX_HASH_SIZE as usize] {
446                        // Complete what was started
447                        unsafe {
448                            randomx_calculate_hash_last(self.vm, output_ptr);
449                        }
450                    }
451                    return Err(RandomXError::ParameterError("input was empty".to_string()));
452                };
453                let size_input = input[i].len() as usize;
454                let input_ptr = input[i].as_ptr() as *mut c_void;
455                output_ptr = arr.as_ptr() as *mut c_void;
456                if i == 0 {
457                    // For first iteration
458                    unsafe {
459                        randomx_calculate_hash_first(self.vm, input_ptr, size_input);
460                    }
461                } else {
462                    unsafe {
463                        // For every other iteration
464                        randomx_calculate_hash_next(self.vm, input_ptr, size_input, output_ptr);
465                    }
466                }
467            }
468
469            if i != 0 {
470                // First hash is only available in 2nd iteration
471                if arr == [0; RANDOMX_HASH_SIZE as usize] {
472                    return Err(RandomXError::Other("RandomX hash was zero".to_string()));
473                }
474                let output: Vec<u8> = arr.to_vec();
475                result.push(output);
476            }
477        }
478        Ok(result)
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use crate::{RandomXCache, RandomXDataset, RandomXFlag, RandomXVM};
485
486    #[test]
487    fn lib_alloc_cache() {
488        let flags = RandomXFlag::default();
489        let key = "Key";
490        let cache = RandomXCache::new(flags, key.as_bytes()).expect("Failed to allocate cache");
491        drop(cache);
492    }
493
494    #[test]
495    fn lib_alloc_dataset() {
496        let flags = RandomXFlag::default();
497        let key = "Key";
498        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
499        let dataset = RandomXDataset::new(flags, cache.clone(), 0).expect("Failed to allocate dataset");
500        drop(dataset);
501        drop(cache);
502    }
503
504    #[test]
505    fn lib_alloc_vm() {
506        let flags = RandomXFlag::default();
507        let key = "Key";
508        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
509        let mut vm = RandomXVM::new(flags, Some(cache.clone()), None).expect("Failed to allocate VM");
510        drop(vm);
511        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
512        vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).expect("Failed to allocate VM");
513        drop(dataset);
514        drop(cache);
515        drop(vm);
516    }
517
518    #[test]
519    fn lib_dataset_memory() {
520        let flags = RandomXFlag::default();
521        let key = "Key";
522        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
523        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
524        let memory = dataset.get_data().unwrap_or_else(|_| std::vec::Vec::new());
525        assert!(!memory.is_empty(), "Failed to get dataset memory");
526        let vec = vec![0u8; memory.len() as usize];
527        assert_ne!(memory, vec);
528        drop(dataset);
529        drop(cache);
530    }
531
532    #[test]
533    fn lib_calculate_hash() {
534        let flags = RandomXFlag::get_recommended_flags();
535        let flags2 = flags | RandomXFlag::FLAG_FULL_MEM;
536        let key = "Key";
537        let input = "Input";
538        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
539        let mut vm1 = RandomXVM::new(flags, Some(cache1.clone()), None).unwrap();
540        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
541        let vec = vec![0u8; hash1.len() as usize];
542        assert_ne!(hash1, vec);
543        let reinit_cache = vm1.reinit_cache(cache1.clone());
544        assert!(reinit_cache.is_ok());
545        let hash2 = vm1.calculate_hash(input.as_bytes()).expect("no data");
546        assert_ne!(hash2, vec);
547        assert_eq!(hash1, hash2);
548
549        let cache2 = RandomXCache::new(flags, key.as_bytes()).unwrap();
550        let vm2 = RandomXVM::new(flags, Some(cache2.clone()), None).unwrap();
551        let hash3 = vm2.calculate_hash(input.as_bytes()).expect("no data");
552        assert_eq!(hash2, hash3);
553
554        let cache3 = RandomXCache::new(flags, key.as_bytes()).unwrap();
555        let dataset3 = RandomXDataset::new(flags, cache3.clone(), 0).unwrap();
556        let mut vm3 = RandomXVM::new(flags2, None, Some(dataset3.clone())).unwrap();
557        let hash4 = vm3.calculate_hash(input.as_bytes()).expect("no data");
558        assert_ne!(hash3, vec);
559        let reinit_dataset = vm3.reinit_dataset(dataset3.clone());
560        assert!(reinit_dataset.is_ok());
561        let hash5 = vm3.calculate_hash(input.as_bytes()).expect("no data");
562        assert_ne!(hash4, vec);
563        assert_eq!(hash4, hash5);
564
565        let cache4 = RandomXCache::new(flags, key.as_bytes()).unwrap();
566        let dataset4 = RandomXDataset::new(flags, cache4.clone(), 0).unwrap();
567        let vm4 = RandomXVM::new(flags2, Some(cache4), Some(dataset4.clone())).unwrap();
568        let hash6 = vm3.calculate_hash(input.as_bytes()).expect("no data");
569        assert_eq!(hash5, hash6);
570
571        drop(dataset3);
572        drop(dataset4);
573        drop(cache1);
574        drop(cache2);
575        drop(cache3);
576        drop(vm1);
577        drop(vm2);
578        drop(vm3);
579        drop(vm4);
580    }
581
582    #[test]
583    fn lib_calculate_hash_set() {
584        let flags = RandomXFlag::default();
585        let key = "Key";
586        let inputs = vec!["Input".as_bytes(), "Input 2".as_bytes(), "Inputs 3".as_bytes()];
587        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
588        let vm = RandomXVM::new(flags, Some(cache.clone()), None).unwrap();
589        let hashes = vm.calculate_hash_set(inputs.as_slice()).expect("no data");
590        assert_eq!(inputs.len(), hashes.len());
591        let mut prev_hash = Vec::new();
592        for (i, hash) in hashes.into_iter().enumerate() {
593            let vec = vec![0u8; hash.len() as usize];
594            assert_ne!(hash, vec);
595            assert_ne!(hash, prev_hash);
596            let compare = vm.calculate_hash(inputs[i]).unwrap(); // sanity check
597            assert_eq!(hash, compare);
598            prev_hash = hash;
599        }
600        drop(cache);
601        drop(vm);
602    }
603
604    #[test]
605    fn lib_calculate_hash_is_consistent() {
606        let flags = RandomXFlag::get_recommended_flags();
607        let key = "Key";
608        let input = "Input";
609        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
610        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
611        let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
612        let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
613        assert_eq!(hash, [
614            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
615            172, 253, 155, 204, 111, 183, 213, 157, 155
616        ]);
617        drop(vm);
618        drop(dataset);
619        drop(cache);
620
621        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
622        let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
623        let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
624        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
625        assert_eq!(hash1, [
626            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
627            172, 253, 155, 204, 111, 183, 213, 157, 155
628        ]);
629        drop(vm1);
630        drop(dataset1);
631        drop(cache1);
632    }
633
634    #[test]
635    fn lib_check_cache_and_dataset_lifetimes() {
636        let flags = RandomXFlag::get_recommended_flags();
637        let key = "Key";
638        let input = "Input";
639        let cache = RandomXCache::new(flags, key.as_bytes()).unwrap();
640        let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap();
641        let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap();
642        drop(dataset);
643        drop(cache);
644        let hash = vm.calculate_hash(input.as_bytes()).expect("no data");
645        assert_eq!(hash, [
646            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
647            172, 253, 155, 204, 111, 183, 213, 157, 155
648        ]);
649        drop(vm);
650
651        let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap();
652        let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap();
653        let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap();
654        drop(dataset1);
655        drop(cache1);
656        let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data");
657        assert_eq!(hash1, [
658            114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32,
659            172, 253, 155, 204, 111, 183, 213, 157, 155
660        ]);
661        drop(vm1);
662    }
663}