radix_engine/system/transaction/
intent_processor.rs

1use crate::blueprints::resource::WorktopSubstate;
2use crate::blueprints::transaction_processor::{MultiThreadResult, TxnInstruction};
3use crate::errors::ApplicationError;
4use crate::errors::RuntimeError;
5use crate::internal_prelude::*;
6use crate::kernel::kernel_api::KernelNodeApi;
7use crate::kernel::kernel_api::KernelSubstateApi;
8use crate::system::node_init::type_info_partition;
9use crate::system::type_info::TypeInfoBlueprint;
10use crate::system::type_info::TypeInfoSubstate;
11use radix_engine_interface::api::SystemApi;
12use radix_engine_interface::blueprints::package::BlueprintVersion;
13use radix_engine_interface::blueprints::resource::*;
14use radix_engine_interface::blueprints::transaction_processor::*;
15use radix_native_sdk::resource::{NativeBucket, NativeNonFungibleBucket, Worktop};
16use radix_native_sdk::runtime::LocalAuthZone;
17use radix_transactions::data::TransformHandler;
18use radix_transactions::validation::*;
19use sbor::rust::prelude::*;
20
21#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
22pub enum TransactionProcessorError {
23    BucketNotFound(u32),
24    ProofNotFound(u32),
25    AddressReservationNotFound(u32),
26    AddressNotFound(u32),
27    BlobNotFound(Hash),
28    InvalidCallData(DecodeError),
29    InvalidPackageSchema(DecodeError),
30    NotPackageAddress(error_models::ReferencedNodeId),
31    NotGlobalAddress(error_models::ReferencedNodeId),
32    AuthZoneIsEmpty,
33    InvocationOutputDecodeError(DecodeError),
34    ArgsEncodeError(EncodeError),
35    TotalBlobSizeLimitExceeded,
36}
37
38impl From<TransactionProcessorError> for RuntimeError {
39    fn from(value: TransactionProcessorError) -> Self {
40        Self::ApplicationError(ApplicationError::TransactionProcessorError(value))
41    }
42}
43
44pub enum ResumeResult {
45    YieldToChild(usize, IndexedScryptoValue),
46    YieldToParent(IndexedScryptoValue),
47    VerifyParent(AccessRule),
48    DoneAndYieldToParent(IndexedScryptoValue),
49    Done,
50}
51
52pub struct IntentProcessor<'a, I: TxnInstruction + ManifestDecode + ManifestCategorize> {
53    remaining_instructions: VecDeque<I>,
54    worktop: Worktop,
55    objects: IntentProcessorObjects<'a>,
56    pub instruction_index: usize,
57    pub outputs: Vec<InstructionOutput>,
58}
59
60impl<'a, I: TxnInstruction + ManifestDecode + ManifestCategorize> IntentProcessor<'a, I> {
61    pub fn init<Y: SystemApi<RuntimeError> + KernelNodeApi + KernelSubstateApi<L>, L: Default>(
62        manifest_encoded_instructions: &[u8],
63        global_address_reservations: &[GlobalAddressReservation],
64        blobs: &'a IndexMap<Hash, Vec<u8>>,
65        max_total_size_of_blobs: usize,
66        api: &mut Y,
67    ) -> Result<Self, RuntimeError> {
68        // Create a worktop
69        let worktop_node_id = api.kernel_allocate_node_id(EntityType::InternalGenericComponent)?;
70        api.kernel_create_node(
71            worktop_node_id,
72            btreemap!(
73                MAIN_BASE_PARTITION => btreemap!(
74                    WorktopField::Worktop.into() => IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(WorktopSubstate::new()))
75                ),
76                TYPE_INFO_FIELD_PARTITION => type_info_partition(
77                    TypeInfoSubstate::Object(ObjectInfo {
78                        blueprint_info: BlueprintInfo {
79                            blueprint_id: BlueprintId::new(&RESOURCE_PACKAGE, WORKTOP_BLUEPRINT),
80                            blueprint_version: BlueprintVersion::default(),
81                            generic_substitutions: Vec::new(),
82                            outer_obj_info: OuterObjectInfo::default(),
83                            features: indexset!(),
84                        },
85                        object_type: ObjectType::Owned,
86                    })
87                )
88            ),
89        )?;
90        api.kernel_pin_node(worktop_node_id)?;
91
92        let worktop = Worktop(Own(worktop_node_id));
93        let instructions =
94            manifest_decode::<Vec<I>>(&manifest_encoded_instructions).map_err(|e| {
95                // This error should never occur if being called from root since this is constructed
96                // by the transaction executor. This error is more to protect against application
97                // space calling this function if/when possible
98                RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e))
99            })?;
100        let objects = IntentProcessorObjects::new(
101            blobs,
102            global_address_reservations,
103            max_total_size_of_blobs,
104        );
105        let outputs = Vec::new();
106
107        Ok(Self {
108            remaining_instructions: instructions.into_iter().collect(),
109            instruction_index: 0usize,
110            worktop,
111            objects,
112            outputs,
113        })
114    }
115
116    pub fn resume<Y: SystemApi<RuntimeError> + KernelNodeApi + KernelSubstateApi<L>, L: Default>(
117        &mut self,
118        received_value: Option<IndexedScryptoValue>,
119        api: &mut Y,
120    ) -> Result<ResumeResult, RuntimeError> {
121        if let Some(received_value) = received_value {
122            self.objects
123                .handle_call_return_data(&received_value, &self.worktop, api)?;
124        }
125
126        while let Some(instruction) = self.remaining_instructions.pop_front() {
127            api.update_instruction_index(self.instruction_index)?;
128            let (output, yield_instruction) =
129                instruction.execute(&mut self.worktop, &mut self.objects, api)?;
130            self.outputs.push(output);
131            self.instruction_index += 1;
132
133            if let Some(yield_instruction) = yield_instruction {
134                let result = match yield_instruction {
135                    MultiThreadResult::VerifyParent(rule) => ResumeResult::VerifyParent(rule),
136                    MultiThreadResult::SwitchToChild(child, value) => ResumeResult::YieldToChild(
137                        child,
138                        IndexedScryptoValue::from_scrypto_value(value),
139                    ),
140                    MultiThreadResult::SwitchToParent(value) => {
141                        if self.remaining_instructions.is_empty() {
142                            self.worktop.drop(api)?;
143                            ResumeResult::DoneAndYieldToParent(
144                                IndexedScryptoValue::from_scrypto_value(value),
145                            )
146                        } else {
147                            ResumeResult::YieldToParent(IndexedScryptoValue::from_scrypto_value(
148                                value,
149                            ))
150                        }
151                    }
152                };
153                return Ok(result);
154            }
155        }
156
157        self.worktop.drop(api)?;
158        Ok(ResumeResult::Done)
159    }
160}
161
162pub struct NextCallReturnsChecker {
163    pub constraints: ManifestResourceConstraints,
164    pub prevent_unspecified_resource_balances: bool,
165    pub aggregate_balances: AggregateResourceBalances,
166}
167
168impl NextCallReturnsChecker {
169    fn validate(self) -> Result<(), RuntimeError> {
170        let result = if self.prevent_unspecified_resource_balances {
171            self.aggregate_balances.validate_only(self.constraints)
172        } else {
173            self.aggregate_balances.validate_includes(self.constraints)
174        };
175        result.map_err(|error| {
176            RuntimeError::SystemError(SystemError::IntentError(
177                IntentError::AssertNextCallReturnsFailed(error),
178            ))
179        })
180    }
181}
182
183pub struct IntentProcessorObjects<'a> {
184    bucket_mapping: NonIterMap<ManifestBucket, NodeId>,
185    pub proof_mapping: IndexMap<ManifestProof, NodeId>,
186    address_reservation_mapping: NonIterMap<ManifestAddressReservation, NodeId>,
187    address_mapping: NonIterMap<ManifestNamedAddress, NodeId>,
188    id_allocator: ManifestIdAllocator,
189    blobs_by_hash: &'a IndexMap<Hash, Vec<u8>>,
190    max_total_size_of_blobs: usize,
191
192    pub next_call_return_constraints: Option<NextCallReturnsChecker>,
193}
194
195impl<'a> IntentProcessorObjects<'a> {
196    fn new(
197        blobs_by_hash: &'a IndexMap<Hash, Vec<u8>>,
198        global_address_reservations: &[GlobalAddressReservation],
199        max_total_size_of_blobs: usize,
200    ) -> Self {
201        let mut processor = Self {
202            blobs_by_hash,
203            proof_mapping: index_map_new(),
204            bucket_mapping: NonIterMap::new(),
205            address_reservation_mapping: NonIterMap::new(),
206            address_mapping: NonIterMap::new(),
207            id_allocator: ManifestIdAllocator::new(),
208            max_total_size_of_blobs,
209            next_call_return_constraints: None,
210        };
211
212        for address_reservation in global_address_reservations {
213            processor
214                .create_manifest_address_reservation(address_reservation.clone())
215                .unwrap();
216        }
217        processor
218    }
219
220    pub fn get_bucket(&mut self, bucket_id: &ManifestBucket) -> Result<Bucket, RuntimeError> {
221        let real_id =
222            self.bucket_mapping
223                .get(bucket_id)
224                .cloned()
225                .ok_or(RuntimeError::ApplicationError(
226                    ApplicationError::TransactionProcessorError(
227                        TransactionProcessorError::BucketNotFound(bucket_id.0),
228                    ),
229                ))?;
230        Ok(Bucket(Own(real_id)))
231    }
232
233    pub fn take_bucket(&mut self, bucket_id: &ManifestBucket) -> Result<Bucket, RuntimeError> {
234        let real_id =
235            self.bucket_mapping
236                .remove(bucket_id)
237                .ok_or(RuntimeError::ApplicationError(
238                    ApplicationError::TransactionProcessorError(
239                        TransactionProcessorError::BucketNotFound(bucket_id.0),
240                    ),
241                ))?;
242        Ok(Bucket(Own(real_id)))
243    }
244
245    pub fn get_blob(&mut self, blob_ref: &ManifestBlobRef) -> Result<&[u8], RuntimeError> {
246        let hash = Hash(blob_ref.0);
247        self.blobs_by_hash
248            .get(&hash)
249            .map(|x| x.as_ref())
250            .ok_or(RuntimeError::ApplicationError(
251                ApplicationError::TransactionProcessorError(
252                    TransactionProcessorError::BlobNotFound(hash),
253                ),
254            ))
255    }
256
257    pub fn get_proof(&mut self, proof_id: &ManifestProof) -> Result<Proof, RuntimeError> {
258        let real_id =
259            self.proof_mapping
260                .get(proof_id)
261                .cloned()
262                .ok_or(RuntimeError::ApplicationError(
263                    ApplicationError::TransactionProcessorError(
264                        TransactionProcessorError::ProofNotFound(proof_id.0),
265                    ),
266                ))?;
267        Ok(Proof(Own(real_id)))
268    }
269
270    pub fn get_address(
271        &mut self,
272        address_id: &ManifestNamedAddress,
273    ) -> Result<NodeId, RuntimeError> {
274        let real_id =
275            self.address_mapping
276                .get(address_id)
277                .cloned()
278                .ok_or(RuntimeError::ApplicationError(
279                    ApplicationError::TransactionProcessorError(
280                        TransactionProcessorError::AddressNotFound(address_id.0),
281                    ),
282                ))?;
283        Ok(real_id)
284    }
285
286    pub fn take_proof(&mut self, proof_id: &ManifestProof) -> Result<Proof, RuntimeError> {
287        let real_id =
288            self.proof_mapping
289                .swap_remove(proof_id)
290                .ok_or(RuntimeError::ApplicationError(
291                    ApplicationError::TransactionProcessorError(
292                        TransactionProcessorError::ProofNotFound(proof_id.0),
293                    ),
294                ))?;
295        Ok(Proof(Own(real_id)))
296    }
297
298    pub fn take_address_reservation(
299        &mut self,
300        address_reservation_id: &ManifestAddressReservation,
301    ) -> Result<GlobalAddressReservation, RuntimeError> {
302        let real_id = self
303            .address_reservation_mapping
304            .remove(address_reservation_id)
305            .ok_or(RuntimeError::ApplicationError(
306                ApplicationError::TransactionProcessorError(
307                    TransactionProcessorError::AddressReservationNotFound(address_reservation_id.0),
308                ),
309            ))?;
310        Ok(GlobalAddressReservation(Own(real_id)))
311    }
312
313    pub fn create_manifest_bucket(&mut self, bucket: Bucket) -> Result<(), RuntimeError> {
314        let new_id = self.id_allocator.new_bucket_id();
315        self.bucket_mapping.insert(new_id.clone(), bucket.0.into());
316        Ok(())
317    }
318
319    pub fn create_manifest_proof(&mut self, proof: Proof) -> Result<(), RuntimeError> {
320        let new_id = self.id_allocator.new_proof_id();
321        self.proof_mapping.insert(new_id.clone(), proof.0.into());
322        Ok(())
323    }
324
325    pub fn create_manifest_address_reservation(
326        &mut self,
327        address_reservation: GlobalAddressReservation,
328    ) -> Result<(), RuntimeError> {
329        let new_id = self.id_allocator.new_address_reservation_id();
330        self.address_reservation_mapping
331            .insert(new_id, address_reservation.0.into());
332        Ok(())
333    }
334
335    pub fn create_manifest_address(&mut self, address: GlobalAddress) -> Result<(), RuntimeError> {
336        let new_id = self.id_allocator.new_address_id();
337        self.address_mapping.insert(new_id, address.into());
338        Ok(())
339    }
340
341    pub fn resolve_package_address(
342        &mut self,
343        address: ManifestPackageAddress,
344    ) -> Result<PackageAddress, RuntimeError> {
345        match address {
346            ManifestPackageAddress::Static(address) => Ok(address),
347            ManifestPackageAddress::Named(name) => {
348                let node_id = self.get_address(&name)?;
349                PackageAddress::try_from(node_id.0).map_err(|_| {
350                    RuntimeError::ApplicationError(ApplicationError::TransactionProcessorError(
351                        TransactionProcessorError::NotPackageAddress(node_id.into()),
352                    ))
353                })
354            }
355        }
356    }
357
358    pub fn resolve_global_address(
359        &mut self,
360        address: ManifestGlobalAddress,
361    ) -> Result<GlobalAddress, RuntimeError> {
362        match address {
363            ManifestGlobalAddress::Static(address) => Ok(address),
364            ManifestGlobalAddress::Named(name) => {
365                let node_id = self.get_address(&name)?;
366                GlobalAddress::try_from(node_id.0).map_err(|_| {
367                    RuntimeError::ApplicationError(ApplicationError::TransactionProcessorError(
368                        TransactionProcessorError::NotGlobalAddress(node_id.into()),
369                    ))
370                })
371            }
372        }
373    }
374
375    pub fn handle_call_return_data<
376        Y: SystemApi<RuntimeError> + KernelSubstateApi<L>,
377        L: Default,
378    >(
379        &mut self,
380        value: &IndexedScryptoValue,
381        worktop: &Worktop,
382        api: &mut Y,
383    ) -> Result<(), RuntimeError> {
384        let mut resource_constraint_checker = self.next_call_return_constraints.take();
385
386        // Auto move into worktop & auth_zone
387        for node_id in value.owned_nodes() {
388            let info = TypeInfoBlueprint::get_type(node_id, api)?;
389            match info {
390                TypeInfoSubstate::Object(info) => match (
391                    info.blueprint_info.blueprint_id.package_address,
392                    info.blueprint_info.blueprint_id.blueprint_name.as_str(),
393                ) {
394                    (RESOURCE_PACKAGE, FUNGIBLE_BUCKET_BLUEPRINT) => {
395                        let bucket = Bucket(Own(node_id.clone()));
396                        if let Some(checker) = &mut resource_constraint_checker {
397                            let resource_address = info
398                                .blueprint_info
399                                .outer_obj_info
400                                .expect()
401                                .try_into()
402                                .unwrap();
403                            checker
404                                .aggregate_balances
405                                .add_fungible(resource_address, bucket.amount(api)?);
406                        }
407                        worktop.put(bucket, api)?;
408                    }
409                    (RESOURCE_PACKAGE, NON_FUNGIBLE_BUCKET_BLUEPRINT) => {
410                        let bucket = Bucket(Own(node_id.clone()));
411                        if let Some(checker) = &mut resource_constraint_checker {
412                            let resource_address = info
413                                .blueprint_info
414                                .outer_obj_info
415                                .expect()
416                                .try_into()
417                                .unwrap();
418                            checker.aggregate_balances.add_non_fungible(
419                                resource_address,
420                                bucket.non_fungible_local_ids(api)?,
421                            );
422                        }
423                        worktop.put(bucket, api)?;
424                    }
425                    (RESOURCE_PACKAGE, FUNGIBLE_PROOF_BLUEPRINT)
426                    | (RESOURCE_PACKAGE, NON_FUNGIBLE_PROOF_BLUEPRINT) => {
427                        let proof = Proof(Own(node_id.clone()));
428                        LocalAuthZone::push(proof, api)?;
429                    }
430                    _ => {
431                        // No-op, but can be extended
432                    }
433                },
434                TypeInfoSubstate::KeyValueStore(_)
435                | TypeInfoSubstate::GlobalAddressReservation(_)
436                | TypeInfoSubstate::GlobalAddressPhantom(_) => {
437                    // No-op, but can be extended
438                }
439            }
440        }
441
442        if let Some(checker) = resource_constraint_checker {
443            checker.validate()?;
444        }
445
446        Ok(())
447    }
448}
449
450pub struct IntentProcessorObjectsWithApi<'a, 'e, Y: SystemApi<RuntimeError>> {
451    pub(crate) worktop: &'a mut Worktop,
452    pub(crate) objects: &'a mut IntentProcessorObjects<'e>,
453    pub(crate) api: &'a mut Y,
454    pub(crate) current_total_size_of_blobs: usize,
455}
456
457impl<'a, 'e, Y: SystemApi<RuntimeError>> TransformHandler<RuntimeError>
458    for IntentProcessorObjectsWithApi<'a, 'e, Y>
459{
460    fn replace_bucket(&mut self, b: ManifestBucket) -> Result<Own, RuntimeError> {
461        self.objects.take_bucket(&b).map(|x| x.0)
462    }
463
464    fn replace_proof(&mut self, p: ManifestProof) -> Result<Own, RuntimeError> {
465        self.objects.take_proof(&p).map(|x| x.0)
466    }
467
468    fn replace_address_reservation(
469        &mut self,
470        r: ManifestAddressReservation,
471    ) -> Result<Own, RuntimeError> {
472        self.objects.take_address_reservation(&r).map(|x| x.0)
473    }
474
475    fn replace_named_address(
476        &mut self,
477        a: ManifestNamedAddress,
478    ) -> Result<Reference, RuntimeError> {
479        self.objects.get_address(&a).map(|x| Reference(x))
480    }
481
482    fn replace_expression(&mut self, e: ManifestExpression) -> Result<Vec<Own>, RuntimeError> {
483        match e {
484            ManifestExpression::EntireWorktop => {
485                let buckets = self.worktop.drain(self.api)?;
486                Ok(buckets.into_iter().map(|b| b.0).collect())
487            }
488            ManifestExpression::EntireAuthZone => {
489                let proofs = LocalAuthZone::drain(self.api)?;
490                Ok(proofs.into_iter().map(|p| p.0).collect())
491            }
492        }
493    }
494
495    fn replace_blob(&mut self, b: ManifestBlobRef) -> Result<Vec<u8>, RuntimeError> {
496        let max_total_size_of_blobs = self.objects.max_total_size_of_blobs;
497        let blob = self.objects.get_blob(&b)?;
498
499        if let Some(new_total) = self.current_total_size_of_blobs.checked_add(blob.len()) {
500            if new_total <= max_total_size_of_blobs {
501                self.current_total_size_of_blobs = new_total;
502                return Ok(blob.to_vec());
503            }
504        }
505
506        Err(RuntimeError::ApplicationError(
507            ApplicationError::TransactionProcessorError(
508                TransactionProcessorError::TotalBlobSizeLimitExceeded,
509            ),
510        ))
511    }
512}