1use super::parsed_module::{CompilationContext, ParsedModule, VersionedContractCodeCostInputs};
2#[cfg(any(test, feature = "testutils"))]
3use crate::budget::AsBudget;
4use crate::{
5 budget::get_wasmi_config,
6 host::metered_clone::MeteredClone,
7 xdr::{Hash, ScErrorCode, ScErrorType},
8 Host, HostError,
9};
10use std::{
11 collections::BTreeMap,
12 sync::{Arc, Mutex, MutexGuard},
13};
14
15#[derive(Clone, Default)]
21pub struct ModuleCache {
22 pub(crate) wasmi_engine: wasmi::Engine,
23 pub(crate) wasmi_linker: wasmi::Linker<Host>,
24 modules: ModuleCacheMap,
25}
26
27static_assertions::assert_impl_all!(ModuleCache: Send, Sync);
31
32#[derive(Clone)]
46struct ModuleCacheMap(Arc<Mutex<BTreeMap<Hash, Arc<ParsedModule>>>>);
47
48impl Default for ModuleCacheMap {
49 fn default() -> Self {
50 Self(Arc::new(Mutex::new(BTreeMap::new())))
51 }
52}
53
54impl ModuleCacheMap {
55 fn lock_map(
56 map: &Arc<Mutex<BTreeMap<Hash, Arc<ParsedModule>>>>,
57 ) -> Result<MutexGuard<'_, BTreeMap<Hash, Arc<ParsedModule>>>, HostError> {
58 map.lock()
59 .map_err(|_| HostError::from((ScErrorType::Context, ScErrorCode::InternalError)))
60 }
61
62 fn contains_key(&self, key: &Hash) -> Result<bool, HostError> {
63 Ok(Self::lock_map(&self.0)?.contains_key(key))
64 }
65
66 fn get(&self, key: &Hash) -> Result<Option<Arc<ParsedModule>>, HostError> {
67 Ok(Self::lock_map(&self.0)?.get(key).map(|rc| rc.clone()))
68 }
69
70 fn insert(&self, key: Hash, value: Arc<ParsedModule>) -> Result<(), HostError> {
71 Self::lock_map(&self.0)?.insert(key, value);
72 Ok(())
73 }
74
75 fn clear(&self) -> Result<(), HostError> {
76 Self::lock_map(&self.0)?.clear();
77 Ok(())
78 }
79
80 fn remove(&self, key: &Hash) -> Result<Option<Arc<ParsedModule>>, HostError> {
81 Ok(Self::lock_map(&self.0)?.remove(key))
82 }
83}
84
85impl ModuleCache {
86 pub fn new<Ctx: CompilationContext>(context: &Ctx) -> Result<Self, HostError> {
87 let wasmi_config = get_wasmi_config(context.as_budget())?;
88 let wasmi_engine = wasmi::Engine::new(&wasmi_config);
89 let modules = ModuleCacheMap::default();
90 let wasmi_linker = Host::make_maximal_wasmi_linker(context, &wasmi_engine)?;
91 Ok(Self {
92 wasmi_engine,
93 modules,
94 wasmi_linker,
95 })
96 }
97
98 #[cfg(any(test, feature = "testutils"))]
99 pub fn add_stored_contracts(&self, host: &Host) -> Result<(), HostError> {
100 use crate::xdr::{ContractCodeEntry, ContractCodeEntryExt, LedgerEntryData, LedgerKey};
101 let storage = host.try_borrow_storage()?;
102 for (k, v) in storage.map.iter(host.as_budget())? {
103 if let LedgerKey::ContractCode(_) = &**k {
104 if let Some((e, _)) = v {
105 if let LedgerEntryData::ContractCode(ContractCodeEntry { code, hash, ext }) =
106 &e.data
107 {
108 if code.as_slice().is_empty() {
115 continue;
116 }
117
118 let code_cost_inputs = match ext {
119 ContractCodeEntryExt::V0 => VersionedContractCodeCostInputs::V0 {
120 wasm_bytes: code.len(),
121 },
122 ContractCodeEntryExt::V1(v1) => VersionedContractCodeCostInputs::V1(
123 v1.cost_inputs.metered_clone(host.as_budget())?,
124 ),
125 };
126 self.parse_and_cache_module(
127 host,
128 host.get_ledger_protocol_version()?,
129 hash,
130 code,
131 code_cost_inputs,
132 )?;
133 }
134 }
135 }
136 }
137 Ok(())
138 }
139
140 pub fn parse_and_cache_module_simple<Ctx: CompilationContext>(
141 &self,
142 context: &Ctx,
143 curr_ledger_protocol: u32,
144 wasm: &[u8],
145 ) -> Result<(), HostError> {
146 let contract_id = Hash(crate::crypto::sha256_hash_from_bytes_raw(
147 wasm,
148 context.as_budget(),
149 )?);
150 self.parse_and_cache_module(
151 context,
152 curr_ledger_protocol,
153 &contract_id,
154 wasm,
155 VersionedContractCodeCostInputs::V0 {
156 wasm_bytes: wasm.len(),
157 },
158 )
159 }
160
161 pub fn parse_and_cache_module<Ctx: CompilationContext>(
162 &self,
163 context: &Ctx,
164 curr_ledger_protocol: u32,
165 contract_id: &Hash,
166 wasm: &[u8],
167 cost_inputs: VersionedContractCodeCostInputs,
168 ) -> Result<(), HostError> {
169 if self.modules.contains_key(contract_id)? {
170 return Ok(());
171 }
172 let parsed_module = ParsedModule::new(
173 context,
174 curr_ledger_protocol,
175 &self.wasmi_engine,
176 &wasm,
177 cost_inputs,
178 )?;
179 self.modules.insert(
180 contract_id.metered_clone(context.as_budget())?,
181 parsed_module,
182 )?;
183 Ok(())
184 }
185
186 pub fn contains_module(&self, wasm_hash: &Hash) -> Result<bool, HostError> {
187 self.modules.contains_key(wasm_hash)
188 }
189
190 pub fn get_module(&self, wasm_hash: &Hash) -> Result<Option<Arc<ParsedModule>>, HostError> {
191 if let Some(m) = self.modules.get(wasm_hash)? {
192 Ok(Some(m.clone()))
193 } else {
194 Ok(None)
195 }
196 }
197
198 pub fn remove_module(&self, wasm_hash: &Hash) -> Result<Option<Arc<ParsedModule>>, HostError> {
199 self.modules.remove(wasm_hash)
200 }
201
202 pub fn clear(&self) -> Result<(), HostError> {
203 self.modules.clear()
204 }
205}