radix_engine/system/
system_type_checker.rs

1use super::payload_validation::*;
2use crate::errors::{RuntimeError, SystemError};
3use crate::internal_prelude::*;
4use crate::system::system::SystemService;
5use crate::system::system_callback::*;
6use crate::system::system_substates::{FieldSubstate, KeyValueEntrySubstate, LockStatus};
7use crate::track::interface::NodeSubstates;
8use radix_blueprint_schema_init::KeyValueStoreGenericSubstitutions;
9use radix_engine_interface::api::field_api::LockFlags;
10use radix_engine_interface::api::{CollectionIndex, FieldValue, KVEntry};
11use radix_engine_interface::blueprints::package::*;
12use sbor::rust::vec::Vec;
13
14/// Metadata for schema validation to help with location of certain schemas
15/// since location of schemas are somewhat scattered
16#[derive(Debug, Clone)]
17pub enum SchemaValidationMeta {
18    ExistingObject {
19        additional_schemas: NodeId,
20    },
21    NewObject {
22        additional_schemas: NonIterMap<SchemaHash, VersionedScryptoSchema>,
23    },
24    Blueprint,
25}
26
27/// The blueprint type to check against along with any additional metadata
28/// required to perform validation
29#[derive(Debug, Clone)]
30pub struct BlueprintTypeTarget {
31    pub blueprint_info: BlueprintInfo,
32    pub meta: SchemaValidationMeta,
33}
34
35/// The key value store to check against along with any additional metadata
36/// required to perform validation
37#[derive(Debug, Clone)]
38pub struct KVStoreTypeTarget {
39    pub kv_store_type: KeyValueStoreGenericSubstitutions,
40    pub meta: NodeId,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
44pub enum TypeCheckError {
45    InvalidNumberOfGenericArgs { expected: usize, actual: usize },
46    InvalidLocalTypeId(LocalTypeId),
47    InvalidBlueprintTypeIdentifier(BlueprintTypeIdentifier),
48    InvalidCollectionIndex(Box<BlueprintInfo>, CollectionIndex),
49    BlueprintPayloadDoesNotExist(Box<BlueprintInfo>, BlueprintPayloadIdentifier),
50    BlueprintPayloadValidationError(Box<BlueprintInfo>, BlueprintPayloadIdentifier, String),
51    KeyValueStorePayloadValidationError(KeyOrValue, String),
52    InstanceSchemaNotFound,
53    MissingSchema,
54}
55
56impl<'a, Y: SystemBasedKernelApi> SystemService<'a, Y> {
57    /// Validate that the type substitutions match the generic definition of a given blueprint
58    pub fn validate_bp_generic_args(
59        &mut self,
60        blueprint_interface: &BlueprintInterface,
61        schemas: &IndexMap<SchemaHash, VersionedScryptoSchema>,
62        generic_substitutions: &Vec<GenericSubstitution>,
63    ) -> Result<(), TypeCheckError> {
64        let generics = &blueprint_interface.generics;
65
66        if !generics.len().eq(&generic_substitutions.len()) {
67            return Err(TypeCheckError::InvalidNumberOfGenericArgs {
68                expected: generics.len(),
69                actual: generic_substitutions.len(),
70            });
71        }
72
73        for generic_substitution in generic_substitutions {
74            Self::validate_generic_substitution(self, schemas, generic_substitution)?;
75        }
76
77        Ok(())
78    }
79
80    /// Validate that the type substitutions for a kv store exist in a given schema
81    pub fn validate_kv_store_generic_args(
82        &mut self,
83        schemas: &IndexMap<SchemaHash, VersionedScryptoSchema>,
84        key: &GenericSubstitution,
85        value: &GenericSubstitution,
86    ) -> Result<(), TypeCheckError> {
87        Self::validate_generic_substitution(self, schemas, key)?;
88        Self::validate_generic_substitution(self, schemas, value)?;
89
90        Ok(())
91    }
92
93    fn validate_generic_substitution(
94        &mut self,
95        schemas: &IndexMap<SchemaHash, VersionedScryptoSchema>,
96        substitution: &GenericSubstitution,
97    ) -> Result<(), TypeCheckError> {
98        match substitution {
99            GenericSubstitution::Local(type_id) => {
100                let schema = schemas
101                    .get(&type_id.0)
102                    .ok_or_else(|| TypeCheckError::MissingSchema)?;
103
104                if schema.v1().resolve_type_kind(type_id.1).is_none() {
105                    Err(TypeCheckError::InvalidLocalTypeId(type_id.1))
106                } else {
107                    Ok(())
108                }
109            }
110            GenericSubstitution::Remote(type_id) => self
111                .get_blueprint_type_schema(type_id)
112                .map(|_| ())
113                .map_err(|_| TypeCheckError::InvalidBlueprintTypeIdentifier(type_id.clone())),
114        }
115    }
116
117    pub fn get_payload_schema(
118        &mut self,
119        target: &BlueprintTypeTarget,
120        payload_identifier: &BlueprintPayloadIdentifier,
121    ) -> Result<
122        (
123            Rc<VersionedScryptoSchema>,
124            LocalTypeId,
125            bool,
126            bool,
127            SchemaOrigin,
128        ),
129        RuntimeError,
130    > {
131        let blueprint_definition =
132            self.get_blueprint_default_definition(target.blueprint_info.blueprint_id.clone())?;
133
134        let (payload_def, allow_ownership, allow_non_global_ref) = blueprint_definition
135            .interface
136            .get_payload_def(payload_identifier)
137            .ok_or_else(|| {
138                RuntimeError::SystemError(SystemError::TypeCheckError(
139                    TypeCheckError::BlueprintPayloadDoesNotExist(
140                        Box::new(target.blueprint_info.clone()),
141                        payload_identifier.clone(),
142                    ),
143                ))
144            })?;
145
146        // Given the payload definition, retrieve the info to be able to do schema validation on a payload
147        let (schema, index, schema_origin) = match payload_def {
148            BlueprintPayloadDef::Static(type_identifier) => {
149                let schema = self.get_schema(
150                    target
151                        .blueprint_info
152                        .blueprint_id
153                        .package_address
154                        .as_node_id(),
155                    &type_identifier.0,
156                )?;
157                (
158                    schema,
159                    type_identifier.1,
160                    SchemaOrigin::Blueprint(target.blueprint_info.blueprint_id.clone()),
161                )
162            }
163            BlueprintPayloadDef::Generic(instance_index) => {
164                let generic_substitution = target
165                    .blueprint_info
166                    .generic_substitutions
167                    .get(instance_index as usize)
168                    .ok_or_else(|| {
169                        RuntimeError::SystemError(SystemError::TypeCheckError(
170                            TypeCheckError::InstanceSchemaNotFound,
171                        ))
172                    })?;
173
174                match generic_substitution {
175                    GenericSubstitution::Local(type_id) => {
176                        let schema = match &target.meta {
177                            SchemaValidationMeta::ExistingObject { additional_schemas } => {
178                                self.get_schema(additional_schemas, &type_id.0)?
179                            }
180                            SchemaValidationMeta::NewObject { additional_schemas } => Rc::new(
181                                additional_schemas
182                                    .get(&type_id.0)
183                                    .ok_or_else(|| {
184                                        RuntimeError::SystemError(SystemError::TypeCheckError(
185                                            TypeCheckError::InstanceSchemaNotFound,
186                                        ))
187                                    })?
188                                    .clone(),
189                            ),
190                            SchemaValidationMeta::Blueprint => {
191                                return Err(RuntimeError::SystemError(
192                                    SystemError::TypeCheckError(
193                                        TypeCheckError::InstanceSchemaNotFound,
194                                    ),
195                                ));
196                            }
197                        };
198
199                        (schema, type_id.1, SchemaOrigin::Instance)
200                    }
201                    GenericSubstitution::Remote(type_id) => {
202                        let (schema, scoped_type_id) = self.get_blueprint_type_schema(&type_id)?;
203
204                        (
205                            schema,
206                            scoped_type_id.1,
207                            SchemaOrigin::Blueprint(BlueprintId::new(
208                                &type_id.package_address,
209                                type_id.blueprint_name.clone(),
210                            )),
211                        )
212                    }
213                }
214            }
215        };
216
217        Ok((
218            schema,
219            index,
220            allow_ownership,
221            allow_non_global_ref,
222            schema_origin,
223        ))
224    }
225
226    /// Validate that a blueprint payload matches the blueprint's definition of that payload
227    pub fn validate_blueprint_payload(
228        &mut self,
229        target: &BlueprintTypeTarget,
230        payload_identifier: BlueprintPayloadIdentifier,
231        payload: &[u8],
232    ) -> Result<(), RuntimeError> {
233        let (schema, index, allow_ownership, allow_non_global_ref, schema_origin) =
234            self.get_payload_schema(target, &payload_identifier)?;
235
236        self.validate_payload(
237            payload,
238            &schema,
239            index,
240            schema_origin,
241            allow_ownership,
242            allow_non_global_ref,
243            BLUEPRINT_PAYLOAD_MAX_DEPTH,
244        )
245        .map_err(|err| {
246            RuntimeError::SystemError(SystemError::TypeCheckError(
247                TypeCheckError::BlueprintPayloadValidationError(
248                    Box::new(target.blueprint_info.clone()),
249                    payload_identifier,
250                    err.error_message(schema.v1()),
251                ),
252            ))
253        })?;
254
255        Ok(())
256    }
257
258    /// Validate that a blueprint kv collection payloads match the blueprint's definition
259    pub fn validate_blueprint_kv_collection(
260        &mut self,
261        target: &BlueprintTypeTarget,
262        collection_index: CollectionIndex,
263        payloads: &[(&Vec<u8>, &Vec<u8>)],
264    ) -> Result<PartitionDescription, RuntimeError> {
265        let blueprint_definition =
266            self.get_blueprint_default_definition(target.blueprint_info.blueprint_id.clone())?;
267
268        let partition_description = blueprint_definition
269            .interface
270            .state
271            .collections
272            .get(collection_index as usize)
273            .ok_or_else(|| {
274                RuntimeError::SystemError(SystemError::TypeCheckError(
275                    TypeCheckError::InvalidCollectionIndex(
276                        Box::new(target.blueprint_info.clone()),
277                        collection_index,
278                    ),
279                ))
280            })?
281            .0;
282
283        for (key, value) in payloads {
284            self.validate_blueprint_payload(
285                &target,
286                BlueprintPayloadIdentifier::KeyValueEntry(collection_index, KeyOrValue::Key),
287                key,
288            )?;
289
290            self.validate_blueprint_payload(
291                &target,
292                BlueprintPayloadIdentifier::KeyValueEntry(collection_index, KeyOrValue::Value),
293                value,
294            )?;
295        }
296
297        Ok(partition_description)
298    }
299
300    /// Validate that a key value payload matches the key value store's definition of that payload
301    pub fn validate_kv_store_payload(
302        &mut self,
303        target: &KVStoreTypeTarget,
304        payload_identifier: KeyOrValue,
305        payload: &[u8],
306    ) -> Result<(), RuntimeError> {
307        let type_substitution = match payload_identifier {
308            KeyOrValue::Key => target.kv_store_type.key_generic_substitution.clone(),
309            KeyOrValue::Value => target.kv_store_type.value_generic_substitution.clone(),
310        };
311
312        let allow_ownership = match payload_identifier {
313            KeyOrValue::Key => false,
314            KeyOrValue::Value => target.kv_store_type.allow_ownership,
315        };
316
317        let (schema, local_type_id) = match type_substitution {
318            GenericSubstitution::Local(type_id) => {
319                (self.get_schema(&target.meta, &type_id.0)?, type_id.1)
320            }
321            GenericSubstitution::Remote(type_id) => self
322                .get_blueprint_type_schema(&type_id)
323                .map(|x| (x.0, x.1 .1))?,
324        };
325
326        self.validate_payload(
327            payload,
328            &schema,
329            local_type_id,
330            SchemaOrigin::KeyValueStore,
331            allow_ownership,
332            false,
333            KEY_VALUE_STORE_PAYLOAD_MAX_DEPTH,
334        )
335        .map_err(|err| {
336            RuntimeError::SystemError(SystemError::TypeCheckError(
337                TypeCheckError::KeyValueStorePayloadValidationError(
338                    payload_identifier,
339                    err.error_message(schema.v1()),
340                ),
341            ))
342        })?;
343
344        Ok(())
345    }
346
347    fn validate_payload<'s>(
348        &mut self,
349        payload: &[u8],
350        schema: &'s VersionedScryptoSchema,
351        type_id: LocalTypeId,
352        schema_origin: SchemaOrigin,
353        allow_ownership: bool,
354        allow_non_global_ref: bool,
355        depth_limit: usize,
356    ) -> Result<(), LocatedValidationError<'s, ScryptoCustomExtension>> {
357        let validation_context: Box<dyn ValidationContext<Error = RuntimeError>> =
358            Box::new(SystemServiceTypeInfoLookup::new(
359                self,
360                schema_origin,
361                allow_ownership,
362                allow_non_global_ref,
363            ));
364        validate_payload_against_schema::<ScryptoCustomExtension, _>(
365            payload,
366            schema.v1(),
367            type_id,
368            &validation_context,
369            depth_limit,
370        )
371    }
372
373    fn get_schema(
374        &mut self,
375        node_id: &NodeId,
376        schema_hash: &SchemaHash,
377    ) -> Result<Rc<VersionedScryptoSchema>, RuntimeError> {
378        let def = self.system().schema_cache.get(schema_hash);
379        if let Some(schema) = def {
380            return Ok(schema.clone());
381        }
382
383        let handle = self.api().kernel_open_substate_with_default(
384            node_id,
385            SCHEMAS_PARTITION,
386            &SubstateKey::Map(scrypto_encode(schema_hash).unwrap()),
387            LockFlags::read_only(),
388            Some(|| {
389                let kv_entry = KeyValueEntrySubstate::<()>::default();
390                IndexedScryptoValue::from_typed(&kv_entry)
391            }),
392            SystemLockData::default(),
393        )?;
394
395        let substate: KeyValueEntrySubstate<VersionedScryptoSchema> =
396            self.api().kernel_read_substate(handle)?.as_typed().unwrap();
397        self.api().kernel_close_substate(handle)?;
398
399        let schema = Rc::new(substate.into_value().unwrap());
400
401        self.system()
402            .schema_cache
403            .insert(schema_hash.clone(), schema.clone());
404
405        Ok(schema)
406    }
407
408    pub fn get_blueprint_type_schema(
409        &mut self,
410        type_id: &BlueprintTypeIdentifier,
411    ) -> Result<(Rc<VersionedScryptoSchema>, ScopedTypeId), RuntimeError> {
412        let BlueprintTypeIdentifier {
413            package_address,
414            blueprint_name,
415            type_name,
416        } = type_id.clone();
417        let blueprint_definition = self.get_blueprint_default_definition(BlueprintId {
418            package_address,
419            blueprint_name,
420        })?;
421        let scoped_type_id = blueprint_definition.interface.types.get(&type_name).ok_or(
422            RuntimeError::SystemError(SystemError::BlueprintTypeNotFound(type_name.clone())),
423        )?;
424        Ok((
425            self.get_schema(package_address.as_node_id(), &scoped_type_id.0)?,
426            scoped_type_id.clone(),
427        ))
428    }
429}
430
431pub struct SystemMapper;
432
433impl SystemMapper {
434    pub fn system_struct_to_node_substates(
435        schema: &IndexedStateSchema,
436        system_struct: (
437            IndexMap<u8, FieldValue>,
438            IndexMap<u8, IndexMap<Vec<u8>, KVEntry>>,
439        ),
440        base_partition_num: PartitionNumber,
441    ) -> NodeSubstates {
442        let mut partitions: NodeSubstates = BTreeMap::new();
443
444        if !system_struct.0.is_empty() {
445            let partition_description = schema.fields_partition().unwrap();
446            let partition_num = match partition_description {
447                PartitionDescription::Physical(partition_num) => partition_num,
448                PartitionDescription::Logical(offset) => {
449                    base_partition_num.at_offset(offset).unwrap()
450                }
451            };
452
453            let mut field_partition = BTreeMap::new();
454
455            for (index, field) in system_struct.0.into_iter() {
456                let (_, field_schema) = schema.field(index).unwrap();
457                match field_schema.transience {
458                    FieldTransience::TransientStatic { .. } => continue,
459                    FieldTransience::NotTransient => {}
460                }
461
462                let value: ScryptoRawValue =
463                    scrypto_decode(&field.value).expect("Checked by payload-schema validation");
464
465                let lock_status = if field.locked {
466                    LockStatus::Locked
467                } else {
468                    LockStatus::Unlocked
469                };
470
471                let substate = FieldSubstate::new_field(value, lock_status);
472
473                let value = IndexedScryptoValue::from_typed(&substate);
474                field_partition.insert(SubstateKey::Field(index), value);
475            }
476
477            partitions.insert(partition_num, field_partition);
478        }
479
480        for (collection_index, substates) in system_struct.1 {
481            let (partition_description, _) = schema.get_partition(collection_index).unwrap();
482            let partition_num = match partition_description {
483                PartitionDescription::Physical(partition_num) => partition_num,
484                PartitionDescription::Logical(offset) => {
485                    base_partition_num.at_offset(offset).unwrap()
486                }
487            };
488
489            let mut partition = BTreeMap::new();
490
491            for (key, kv_entry) in substates {
492                let kv_entry = if let Some(value) = kv_entry.value {
493                    let value: ScryptoRawValue = scrypto_decode(&value).unwrap();
494                    let kv_entry = if kv_entry.locked {
495                        KeyValueEntrySubstate::locked_entry(value)
496                    } else {
497                        KeyValueEntrySubstate::unlocked_entry(value)
498                    };
499                    kv_entry
500                } else {
501                    if kv_entry.locked {
502                        KeyValueEntrySubstate::locked_empty_entry()
503                    } else {
504                        continue;
505                    }
506                };
507
508                let value = IndexedScryptoValue::from_typed(&kv_entry);
509                partition.insert(SubstateKey::Map(key), value);
510            }
511
512            partitions.insert(partition_num, partition);
513        }
514
515        partitions
516    }
517}