radix_engine/vm/
vm.rs

1use crate::blueprints::package::*;
2use crate::errors::{ApplicationError, RuntimeError};
3use crate::internal_prelude::*;
4use crate::kernel::kernel_api::{KernelNodeApi, KernelSubstateApi};
5use crate::system::system_callback::*;
6use crate::system::system_callback_api::SystemCallbackObject;
7use crate::system::system_substates::KeyValueEntrySubstate;
8use crate::vm::wasm::{ScryptoV1WasmValidator, WasmEngine};
9use crate::vm::{NativeVm, NativeVmExtension, ScryptoVm};
10use radix_engine_interface::api::field_api::LockFlags;
11use radix_engine_interface::api::SystemApi;
12
13use crate::vm::ScryptoVmVersion;
14
15use super::wasm::DefaultWasmEngine;
16use super::NoExtension;
17
18pub type VmBootSubstate = VmBoot;
19
20#[derive(Debug, Clone, PartialEq, Eq, Sbor, ScryptoSborAssertion)]
21#[sbor_assert(backwards_compatible(cuttlefish = "FILE:vm_boot_substate_cuttlefish_schema.bin",))]
22pub enum VmBoot {
23    V1 { scrypto_version: u64 },
24}
25
26impl VmBoot {
27    /// Loads vm boot from the database, or resolves a fallback.
28    pub fn load(substate_db: &impl SubstateDatabase) -> Self {
29        substate_db
30            .get_substate(
31                TRANSACTION_TRACKER,
32                BOOT_LOADER_PARTITION,
33                BootLoaderField::VmBoot,
34            )
35            .unwrap_or_else(|| Self::babylon_genesis())
36    }
37
38    pub fn latest() -> Self {
39        Self::bottlenose()
40    }
41
42    pub fn bottlenose() -> Self {
43        Self::V1 {
44            scrypto_version: ScryptoVmVersion::V1_1.into(),
45        }
46    }
47
48    pub fn babylon_genesis() -> Self {
49        Self::V1 {
50            scrypto_version: ScryptoVmVersion::V1_0.into(),
51        }
52    }
53}
54
55pub trait VmApi {
56    fn get_scrypto_version(&self) -> ScryptoVmVersion;
57}
58
59impl VmApi for VmBoot {
60    fn get_scrypto_version(&self) -> ScryptoVmVersion {
61        match self {
62            VmBoot::V1 { scrypto_version } => ScryptoVmVersion::try_from(*scrypto_version)
63                .expect(&format!("Unexpected scrypto version: {}", scrypto_version)),
64        }
65    }
66}
67
68/// This trait is intended to encapsulate the data and types required to
69/// initalize the VMs in the engine.
70///
71/// The canonical implementation is [`VmModules`] - but we have this trait
72/// as well so that functions can take an `&impl VmInitialize` parameter,
73/// and we can avoid the proliferation of [`WasmEngine`] and
74/// [`NativeVmExtension`] generics across the codebase.
75pub trait VmInitialize {
76    type WasmEngine: WasmEngine;
77    type NativeVmExtension: NativeVmExtension;
78
79    fn get_vm_extension(&self) -> Self::NativeVmExtension;
80
81    fn get_scrypto_vm(&self) -> &ScryptoVm<Self::WasmEngine>;
82}
83
84pub struct VmModules<W: WasmEngine, E: NativeVmExtension> {
85    pub scrypto_vm: ScryptoVm<W>,
86    pub vm_extension: E,
87}
88
89impl<W: WasmEngine, E: NativeVmExtension> VmModules<W, E> {
90    pub fn new(scrypto_vm: ScryptoVm<W>, vm_extension: E) -> Self {
91        Self {
92            scrypto_vm,
93            vm_extension,
94        }
95    }
96}
97
98impl<E: NativeVmExtension> VmModules<DefaultWasmEngine, E> {
99    pub fn default_with_extension(vm_extension: E) -> Self {
100        Self {
101            scrypto_vm: Default::default(),
102            vm_extension,
103        }
104    }
105}
106
107pub type DefaultVmModules = VmModules<DefaultWasmEngine, NoExtension>;
108
109impl DefaultVmModules {
110    pub fn default() -> Self {
111        Self {
112            scrypto_vm: ScryptoVm::default(),
113            vm_extension: NoExtension,
114        }
115    }
116}
117
118impl<W: WasmEngine, E: NativeVmExtension> VmInitialize for VmModules<W, E> {
119    type WasmEngine = W;
120    type NativeVmExtension = E;
121
122    fn get_vm_extension(&self) -> Self::NativeVmExtension {
123        self.vm_extension.clone()
124    }
125
126    fn get_scrypto_vm(&self) -> &ScryptoVm<Self::WasmEngine> {
127        &self.scrypto_vm
128    }
129}
130
131pub struct VmInit<'g, W: WasmEngine, E: NativeVmExtension> {
132    pub scrypto_vm: &'g ScryptoVm<W>,
133    pub native_vm_extension: E,
134    pub vm_boot: VmBoot,
135}
136
137impl<'g, W: WasmEngine, E: NativeVmExtension> InitializationParameters for VmInit<'g, W, E> {
138    type For = Vm<'g, W, E>;
139}
140
141impl<'g, W: WasmEngine, E: NativeVmExtension> VmInit<'g, W, E> {
142    pub fn load(
143        substate_db: &impl SubstateDatabase,
144        vm_modules: &'g impl VmInitialize<WasmEngine = W, NativeVmExtension = E>,
145    ) -> Self {
146        Self {
147            scrypto_vm: vm_modules.get_scrypto_vm(),
148            native_vm_extension: vm_modules.get_vm_extension(),
149            vm_boot: VmBoot::load(substate_db),
150        }
151    }
152}
153
154pub struct Vm<'g, W: WasmEngine, E: NativeVmExtension> {
155    pub scrypto_vm: &'g ScryptoVm<W>,
156    pub native_vm: NativeVm<E>,
157    pub vm_boot: VmBoot,
158}
159
160impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<'g, W, E> {
161    type Init = VmInit<'g, W, E>;
162
163    fn init(vm_init: VmInit<'g, W, E>) -> Result<Self, BootloadingError> {
164        Ok(Self {
165            scrypto_vm: vm_init.scrypto_vm,
166            native_vm: NativeVm::new_with_extension(vm_init.native_vm_extension),
167            vm_boot: vm_init.vm_boot,
168        })
169    }
170
171    fn invoke<
172        Y: SystemApi<RuntimeError>
173            + SystemBasedKernelInternalApi<SystemCallback = Self>
174            + KernelNodeApi
175            + KernelSubstateApi<SystemLockData>,
176    >(
177        address: &PackageAddress,
178        export: PackageExport,
179        input: &IndexedScryptoValue,
180        api: &mut Y,
181    ) -> Result<IndexedScryptoValue, RuntimeError> {
182        let vm_type = {
183            let handle = api.kernel_open_substate_with_default(
184                address.as_node_id(),
185                MAIN_BASE_PARTITION
186                    .at_offset(PACKAGE_VM_TYPE_PARTITION_OFFSET)
187                    .unwrap(),
188                &SubstateKey::Map(scrypto_encode(&export.code_hash).unwrap()),
189                LockFlags::read_only(),
190                Some(|| {
191                    let kv_entry = KeyValueEntrySubstate::<()>::default();
192                    IndexedScryptoValue::from_typed(&kv_entry)
193                }),
194                SystemLockData::default(),
195            )?;
196            let vm_type = api.kernel_read_substate(handle)?;
197            let vm_type: PackageCodeVmTypeEntrySubstate = vm_type.as_typed().unwrap();
198            api.kernel_close_substate(handle)?;
199            vm_type
200                .into_value()
201                .unwrap_or_else(|| panic!("Vm type not found: {:?}", export))
202        };
203
204        let vm_api = api.kernel_get_system().callback.vm_boot.clone();
205
206        let output = match vm_type.fully_update_and_into_latest_version().vm_type {
207            VmType::Native => {
208                let original_code = {
209                    let handle = api.kernel_open_substate_with_default(
210                        address.as_node_id(),
211                        MAIN_BASE_PARTITION
212                            .at_offset(PACKAGE_ORIGINAL_CODE_PARTITION_OFFSET)
213                            .unwrap(),
214                        &SubstateKey::Map(scrypto_encode(&export.code_hash).unwrap()),
215                        LockFlags::read_only(),
216                        Some(|| {
217                            let kv_entry = KeyValueEntrySubstate::<()>::default();
218                            IndexedScryptoValue::from_typed(&kv_entry)
219                        }),
220                        SystemLockData::default(),
221                    )?;
222                    let original_code = api.kernel_read_substate(handle)?;
223                    let original_code: PackageCodeOriginalCodeEntrySubstate =
224                        original_code.as_typed().unwrap();
225                    api.kernel_close_substate(handle)?;
226                    original_code
227                        .into_value()
228                        .unwrap_or_else(|| panic!("Original code not found: {:?}", export))
229                };
230
231                let mut vm_instance = api.kernel_get_system().callback.native_vm.create_instance(
232                    address,
233                    &original_code.fully_update_and_into_latest_version().code,
234                )?;
235                let output =
236                    { vm_instance.invoke(export.export_name.as_str(), input, api, &vm_api)? };
237
238                output
239            }
240            VmType::ScryptoV1 => {
241                let instrumented_code = {
242                    let handle = api.kernel_open_substate_with_default(
243                        address.as_node_id(),
244                        MAIN_BASE_PARTITION
245                            .at_offset(PACKAGE_INSTRUMENTED_CODE_PARTITION_OFFSET)
246                            .unwrap(),
247                        &SubstateKey::Map(scrypto_encode(&export.code_hash).unwrap()),
248                        LockFlags::read_only(),
249                        Some(|| {
250                            let kv_entry = KeyValueEntrySubstate::<()>::default();
251                            IndexedScryptoValue::from_typed(&kv_entry)
252                        }),
253                        SystemLockData::default(),
254                    )?;
255                    let instrumented_code = api.kernel_read_substate(handle)?;
256                    let instrumented_code: PackageCodeInstrumentedCodeEntrySubstate =
257                        instrumented_code.as_typed().unwrap();
258                    api.kernel_close_substate(handle)?;
259                    instrumented_code
260                        .into_value()
261                        .unwrap_or_else(|| panic!("Instrumented code not found: {:?}", export))
262                        .fully_update_and_into_latest_version()
263                };
264
265                let mut scrypto_vm_instance = {
266                    api.kernel_get_system().callback.scrypto_vm.create_instance(
267                        address,
268                        export.code_hash,
269                        &instrumented_code.instrumented_code,
270                    )
271                };
272
273                api.consume_cost_units(ClientCostingEntry::PrepareWasmCode {
274                    size: instrumented_code.instrumented_code.len(),
275                })?;
276
277                let output = {
278                    scrypto_vm_instance.invoke(export.export_name.as_str(), input, api, &vm_api)?
279                };
280
281                output
282            }
283        };
284
285        Ok(output)
286    }
287}
288
289pub trait VmInvoke {
290    // TODO: Remove KernelNodeAPI + KernelSubstateAPI from api, unify with VmApi
291    fn invoke<
292        Y: SystemApi<RuntimeError> + KernelNodeApi + KernelSubstateApi<SystemLockData>,
293        V: VmApi,
294    >(
295        &mut self,
296        export_name: &str,
297        input: &IndexedScryptoValue,
298        api: &mut Y,
299        vm_api: &V,
300    ) -> Result<IndexedScryptoValue, RuntimeError>;
301}
302
303pub struct VmPackageValidation;
304
305impl VmPackageValidation {
306    pub fn validate<V: VmApi>(
307        definition: &PackageDefinition,
308        vm_type: VmType,
309        code: &[u8],
310        vm_api: &V,
311    ) -> Result<Option<Vec<u8>>, RuntimeError> {
312        match vm_type {
313            VmType::Native => Ok(None),
314            VmType::ScryptoV1 => {
315                let version = vm_api.get_scrypto_version();
316
317                // Validate WASM
318                let instrumented_code = ScryptoV1WasmValidator::new(version)
319                    .validate(&code, definition.blueprints.values())
320                    .map_err(|e| {
321                        RuntimeError::ApplicationError(ApplicationError::PackageError(
322                            PackageError::InvalidWasm(e),
323                        ))
324                    })?
325                    .0;
326
327                for BlueprintDefinitionInit {
328                    is_transient,
329                    blueprint_type,
330                    feature_set,
331                    schema:
332                        BlueprintSchemaInit {
333                            generics,
334                            state:
335                                BlueprintStateSchemaInit {
336                                    collections,
337                                    fields,
338                                },
339                            functions,
340                            hooks,
341                            ..
342                        },
343                    ..
344                } in definition.blueprints.values()
345                {
346                    match blueprint_type {
347                        BlueprintType::Outer => {}
348                        BlueprintType::Inner { .. } => {
349                            return Err(RuntimeError::ApplicationError(
350                                ApplicationError::PackageError(PackageError::WasmUnsupported(
351                                    "Inner blueprints not supported".to_string(),
352                                )),
353                            ));
354                        }
355                    }
356
357                    if !feature_set.is_empty() {
358                        return Err(RuntimeError::ApplicationError(
359                            ApplicationError::PackageError(PackageError::WasmUnsupported(
360                                "Feature set not supported".to_string(),
361                            )),
362                        ));
363                    }
364
365                    if !collections.is_empty() {
366                        return Err(RuntimeError::ApplicationError(
367                            ApplicationError::PackageError(PackageError::WasmUnsupported(
368                                "Static collections not supported".to_string(),
369                            )),
370                        ));
371                    }
372
373                    if fields.len() > 1 {
374                        return Err(RuntimeError::ApplicationError(
375                            ApplicationError::PackageError(PackageError::WasmUnsupported(
376                                "More than 1 substate field not supported".to_string(),
377                            )),
378                        ));
379                    }
380
381                    for field in fields {
382                        match &field.condition {
383                            Condition::Always => {}
384                            _ => {
385                                return Err(RuntimeError::ApplicationError(
386                                    ApplicationError::PackageError(PackageError::WasmUnsupported(
387                                        "Conditional fields are not supported".to_string(),
388                                    )),
389                                ));
390                            }
391                        }
392
393                        match field.transience {
394                            FieldTransience::NotTransient => {}
395                            _ => {
396                                return Err(RuntimeError::ApplicationError(
397                                    ApplicationError::PackageError(PackageError::WasmUnsupported(
398                                        "Transient fields are not supported".to_string(),
399                                    )),
400                                ));
401                            }
402                        }
403                    }
404
405                    if !hooks.hooks.is_empty() {
406                        return Err(RuntimeError::ApplicationError(
407                            ApplicationError::PackageError(PackageError::WasmUnsupported(
408                                "Hooks not supported".to_string(),
409                            )),
410                        ));
411                    }
412
413                    for (_name, schema) in &functions.functions {
414                        if let Some(info) = &schema.receiver {
415                            if info.ref_types != RefTypes::NORMAL {
416                                return Err(RuntimeError::ApplicationError(
417                                    ApplicationError::PackageError(PackageError::WasmUnsupported(
418                                        "Irregular ref types not supported".to_string(),
419                                    )),
420                                ));
421                            }
422                        }
423                    }
424
425                    if !generics.is_empty() {
426                        return Err(RuntimeError::ApplicationError(
427                            ApplicationError::PackageError(PackageError::WasmUnsupported(
428                                "Generics not supported".to_string(),
429                            )),
430                        ));
431                    }
432
433                    if *is_transient {
434                        return Err(RuntimeError::ApplicationError(
435                            ApplicationError::PackageError(PackageError::WasmUnsupported(
436                                "Transient blueprints not supported".to_string(),
437                            )),
438                        ));
439                    }
440                }
441                Ok(Some(instrumented_code))
442            }
443        }
444    }
445}