darkfi/validator/
randomx_factory.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 * Copyright (C) 2021 The Tari Project (BSD-3)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20use std::{
21    collections::HashMap,
22    fmt,
23    sync::{Arc, RwLock},
24    time::Instant,
25};
26
27use randomx::{RandomXCache, RandomXFlags, RandomXVM};
28use tracing::{debug, warn};
29
30use crate::Result;
31
32/// The RandomX light mode virtual machine instance used to verify
33/// mining.
34#[derive(Clone)]
35pub struct RandomXVMInstance {
36    instance: Arc<RwLock<RandomXVM>>,
37}
38
39impl RandomXVMInstance {
40    /// Generate a new RandomX virtual machine instance operating in
41    /// light mode. Memory required per VM in light mode is 256MB.
42    fn create(key: &[u8]) -> Result<Self> {
43        let flags = RandomXFlags::get_recommended_flags();
44        let (flags, cache) = match RandomXCache::new(flags, key) {
45            Ok(cache) => (flags, cache),
46            Err(err) => {
47                warn!(target: "validator::randomx", "[VALIDATOR] Error initializing RandomX cache with flags {flags:?}: {err}");
48                warn!(target: "validator::randomx", "[VALIDATOR] Falling back to default flags");
49                let flags = RandomXFlags::DEFAULT;
50                let cache = RandomXCache::new(flags, key)?;
51                (flags, cache)
52            }
53        };
54
55        let vm = RandomXVM::new(flags, Some(cache), None)?;
56        debug!(target: "validator::randomx", "[VALIDATOR] RandomX VM started with flags = {flags:?}");
57
58        Ok(Self { instance: Arc::new(RwLock::new(vm)) })
59    }
60
61    /// Calculate the RandomX mining hash.
62    pub fn calculate_hash(&self, input: &[u8]) -> Result<Vec<u8>> {
63        let lock = self.instance.write().unwrap();
64        Ok(lock.calculate_hash(input)?)
65    }
66}
67
68unsafe impl Send for RandomXVMInstance {}
69unsafe impl Sync for RandomXVMInstance {}
70
71/// The RandomX factory that manages the creation of RandomX VMs.
72#[derive(Clone, Debug)]
73pub struct RandomXFactory {
74    /// Threadsafe impl of the inner impl
75    inner: Arc<RwLock<RandomXFactoryInner>>,
76}
77
78impl Default for RandomXFactory {
79    fn default() -> Self {
80        Self::new(2)
81    }
82}
83
84impl RandomXFactory {
85    /// Create a new RandomXFactory with the specified maximum number of VMs.
86    pub fn new(max_vms: usize) -> Self {
87        Self { inner: Arc::new(RwLock::new(RandomXFactoryInner::new(max_vms))) }
88    }
89
90    /// Create a new RandomX VM instance with the specified key.
91    pub fn create(&self, key: &[u8]) -> Result<RandomXVMInstance> {
92        let res;
93        {
94            let mut inner = self.inner.write().unwrap();
95            res = inner.create(key)?;
96        }
97        Ok(res)
98    }
99
100    /// Auxiliary function to get the number of VMs currently
101    /// allocated.
102    pub fn get_count(&self) -> Result<usize> {
103        let inner = self.inner.read().unwrap();
104        Ok(inner.get_count())
105    }
106}
107
108struct RandomXFactoryInner {
109    vms: HashMap<Vec<u8>, (Instant, RandomXVMInstance)>,
110    max_vms: usize,
111}
112
113impl RandomXFactoryInner {
114    /// Create a new RandomXFactoryInner.
115    pub(crate) fn new(max_vms: usize) -> Self {
116        debug!(target: "validator::randomx", "[VALIDATOR] RandomXFactory started with {max_vms} max VMs");
117        Self { vms: Default::default(), max_vms }
118    }
119
120    /// Create a new RandomXVMInstance.
121    pub(crate) fn create(&mut self, key: &[u8]) -> Result<RandomXVMInstance> {
122        if let Some(entry) = self.vms.get_mut(key) {
123            let vm = entry.1.clone();
124            entry.0 = Instant::now();
125            return Ok(vm)
126        }
127
128        if self.vms.len() >= self.max_vms {
129            if let Some(oldest_key) =
130                self.vms.iter().min_by_key(|(_, (i, _))| *i).map(|(k, _)| k.clone())
131            {
132                self.vms.remove(&oldest_key);
133            }
134        }
135
136        let vm = RandomXVMInstance::create(key)?;
137        self.vms.insert(Vec::from(key), (Instant::now(), vm.clone()));
138        Ok(vm)
139    }
140
141    /// Get the number of VMs currently allocated
142    pub(crate) fn get_count(&self) -> usize {
143        self.vms.len()
144    }
145}
146
147impl fmt::Debug for RandomXFactoryInner {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        f.debug_struct("RandomXFactory").field("max_vms", &self.max_vms).finish()
150    }
151}