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::{collections::HashMap, sync::Arc, time::Instant};
21
22use randomx::{RandomXCache, RandomXFlags, RandomXVM};
23use tracing::{debug, warn};
24
25use crate::Result;
26
27/// Atomic pointer to a RandomX light mode virtual machine instance.
28pub type RandomXVMInstance = Arc<RandomXVM>;
29
30/// The RandomX factory that manages the creation of RandomX VMs.
31#[derive(Clone, Debug)]
32pub struct RandomXFactory {
33    vms: HashMap<Vec<u8>, (Instant, RandomXVMInstance)>,
34    max_vms: usize,
35}
36
37impl Default for RandomXFactory {
38    fn default() -> Self {
39        Self::new(2)
40    }
41}
42
43impl RandomXFactory {
44    /// Create a new RandomXFactory with the specified maximum number
45    /// of VMs.
46    pub fn new(max_vms: usize) -> Self {
47        Self { vms: HashMap::new(), max_vms }
48    }
49
50    /// Create a new RandomX VM instance with the specified key.
51    pub fn create(&mut self, key: &[u8]) -> Result<RandomXVMInstance> {
52        if let Some(entry) = self.vms.get_mut(key) {
53            let vm = entry.1.clone();
54            entry.0 = Instant::now();
55            return Ok(vm)
56        }
57
58        if self.vms.len() >= self.max_vms {
59            if let Some(oldest_key) =
60                self.vms.iter().min_by_key(|(_, (i, _))| *i).map(|(k, _)| k.clone())
61            {
62                self.vms.remove(&oldest_key);
63            }
64        }
65
66        // Generate a new RandomX virtual machine instance operating in
67        // light mode. Memory required per VM in light mode is 256MB.
68        let flags = RandomXFlags::get_recommended_flags();
69        let (flags, cache) = match RandomXCache::new(flags, key) {
70            Ok(cache) => (flags, cache),
71            Err(err) => {
72                warn!(target: "validator::randomx", "[VALIDATOR] Error initializing RandomX cache with flags {flags:?}: {err}");
73                warn!(target: "validator::randomx", "[VALIDATOR] Falling back to default flags");
74                let flags = RandomXFlags::DEFAULT;
75                let cache = RandomXCache::new(flags, key)?;
76                (flags, cache)
77            }
78        };
79
80        let vm = Arc::new(RandomXVM::new(flags, Some(cache), None)?);
81        debug!(target: "validator::randomx", "[VALIDATOR] RandomX VM started with flags = {flags:?}");
82
83        self.vms.insert(Vec::from(key), (Instant::now(), vm.clone()));
84        Ok(vm)
85    }
86
87    /// Auxiliary function to get the number of VMs currently
88    /// allocated.
89    pub fn get_count(&self) -> usize {
90        self.vms.len()
91    }
92}