unc_vm_runner/
cache.rs

1use crate::errors::ContractPrecompilatonResult;
2use crate::logic::errors::{CacheError, CompilationError};
3use crate::logic::{CompiledContract, CompiledContractCache, Config};
4use crate::runner::VMKindExt;
5use crate::ContractCode;
6use borsh::BorshSerialize;
7use std::collections::HashMap;
8use std::fmt;
9use std::sync::{Arc, Mutex};
10use unc_parameters::vm::VMKind;
11use unc_primitives_core::hash::CryptoHash;
12
13#[derive(Debug, Clone, BorshSerialize)]
14enum ContractCacheKey {
15    _Version1,
16    _Version2,
17    _Version3,
18    Version4 {
19        code_hash: CryptoHash,
20        vm_config_non_crypto_hash: u64,
21        vm_kind: VMKind,
22        vm_hash: u64,
23    },
24}
25
26fn vm_hash(vm_kind: VMKind) -> u64 {
27    match vm_kind {
28        #[cfg(all(feature = "wasmer0_vm", target_arch = "x86_64"))]
29        VMKind::Wasmer0 => crate::wasmer_runner::wasmer0_vm_hash(),
30        #[cfg(not(all(feature = "wasmer0_vm", target_arch = "x86_64")))]
31        VMKind::Wasmer0 => panic!("Wasmer0 is not enabled"),
32        #[cfg(all(feature = "wasmer2_vm", target_arch = "x86_64"))]
33        VMKind::Wasmer2 => crate::wasmer2_runner::wasmer2_vm_hash(),
34        #[cfg(not(all(feature = "wasmer2_vm", target_arch = "x86_64")))]
35        VMKind::Wasmer2 => panic!("Wasmer2 is not enabled"),
36        #[cfg(feature = "wasmtime_vm")]
37        VMKind::Wasmtime => crate::wasmtime_runner::wasmtime_vm_hash(),
38        #[cfg(not(feature = "wasmtime_vm"))]
39        VMKind::Wasmtime => panic!("Wasmtime is not enabled"),
40        #[cfg(all(feature = "unc_vm", target_arch = "x86_64"))]
41        VMKind::UncVm => crate::unc_vm_runner::unc_vm_vm_hash(),
42        #[cfg(not(all(feature = "unc_vm", target_arch = "x86_64")))]
43        VMKind::UncVm => panic!("UncVM is not enabled"),
44    }
45}
46
47pub fn get_contract_cache_key(code: &ContractCode, config: &Config) -> CryptoHash {
48    let _span = tracing::debug_span!(target: "vm", "get_key").entered();
49    let key = ContractCacheKey::Version4 {
50        code_hash: *code.hash(),
51        vm_config_non_crypto_hash: config.non_crypto_hash(),
52        vm_kind: config.vm_kind,
53        vm_hash: vm_hash(config.vm_kind),
54    };
55    CryptoHash::hash_borsh(key)
56}
57
58#[derive(Default)]
59pub struct MockCompiledContractCache {
60    store: Arc<Mutex<HashMap<CryptoHash, CompiledContract>>>,
61}
62
63impl MockCompiledContractCache {
64    pub fn len(&self) -> usize {
65        self.store.lock().unwrap().len()
66    }
67}
68
69impl CompiledContractCache for MockCompiledContractCache {
70    fn put(&self, key: &CryptoHash, value: CompiledContract) -> std::io::Result<()> {
71        self.store.lock().unwrap().insert(*key, value);
72        Ok(())
73    }
74
75    fn get(&self, key: &CryptoHash) -> std::io::Result<Option<CompiledContract>> {
76        Ok(self.store.lock().unwrap().get(key).map(Clone::clone))
77    }
78}
79
80impl fmt::Debug for MockCompiledContractCache {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let guard = self.store.lock().unwrap();
83        let hm: &HashMap<_, _> = &*guard;
84        fmt::Debug::fmt(hm, f)
85    }
86}
87
88/// Precompiles contract for the current default VM, and stores result to the cache.
89/// Returns `Ok(true)` if compiled code was added to the cache, and `Ok(false)` if element
90/// is already in the cache, or if cache is `None`.
91pub fn precompile_contract(
92    code: &ContractCode,
93    config: &Config,
94    cache: Option<&dyn CompiledContractCache>,
95) -> Result<Result<ContractPrecompilatonResult, CompilationError>, CacheError> {
96    let _span = tracing::debug_span!(target: "vm", "precompile_contract").entered();
97    let vm_kind = config.vm_kind;
98    let runtime = vm_kind
99        .runtime(config.clone())
100        .unwrap_or_else(|| panic!("the {vm_kind:?} runtime has not been enabled at compile time"));
101    let cache = match cache {
102        Some(it) => it,
103        None => return Ok(Ok(ContractPrecompilatonResult::CacheNotAvailable)),
104    };
105    let key = get_contract_cache_key(code, config);
106    // Check if we already cached with such a key.
107    if cache.has(&key).map_err(CacheError::ReadError)? {
108        return Ok(Ok(ContractPrecompilatonResult::ContractAlreadyInCache));
109    }
110    runtime.precompile(code, cache)
111}