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#[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#[derive(Debug, Clone)]
30pub struct BlueprintTypeTarget {
31 pub blueprint_info: BlueprintInfo,
32 pub meta: SchemaValidationMeta,
33}
34
35#[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 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 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 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 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 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 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}