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 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 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 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 }
433 },
434 TypeInfoSubstate::KeyValueStore(_)
435 | TypeInfoSubstate::GlobalAddressReservation(_)
436 | TypeInfoSubstate::GlobalAddressPhantom(_) => {
437 }
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}