radix_engine/system/transaction/
multithread_intent_processor.rs

1use crate::blueprints::resource::AuthZone;
2use crate::blueprints::transaction_processor::{
3    IntentProcessor, ResumeResult, MAX_TOTAL_BLOB_SIZE_PER_INVOCATION,
4};
5use crate::errors::{KernelError, RuntimeError, SystemError};
6use crate::internal_prelude::*;
7use crate::kernel::kernel_callback_api::KernelCallbackObject;
8use crate::system::actor::{Actor, FunctionActor};
9use crate::system::node_init::type_info_partition;
10use crate::system::system::SystemService;
11use crate::system::system_callback::{System, SystemBasedKernelApi};
12use crate::system::system_modules::auth::{AuthModule, Authorization, AuthorizationCheckResult};
13use crate::system::type_info::TypeInfoSubstate;
14use radix_common::constants::{RESOURCE_PACKAGE, TRANSACTION_PROCESSOR_PACKAGE};
15use radix_common::prelude::{BlueprintId, EntityType, GlobalAddressReservation, Reference};
16use radix_common::types::{GlobalCaller, NodeId};
17use radix_engine_interface::blueprints::package::BlueprintVersion;
18use radix_engine_interface::blueprints::transaction_processor::{
19    TRANSACTION_PROCESSOR_BLUEPRINT, TRANSACTION_PROCESSOR_RUN_IDENT,
20};
21use radix_engine_interface::prelude::{
22    AccessRule, AuthZoneField, BlueprintInfo, ObjectInfo, ObjectType, OuterObjectInfo,
23    AUTH_ZONE_BLUEPRINT, FUNGIBLE_PROOF_BLUEPRINT, MAIN_BASE_PARTITION,
24    NON_FUNGIBLE_PROOF_BLUEPRINT, TYPE_INFO_FIELD_PARTITION,
25};
26use radix_rust::prelude::*;
27use radix_transactions::model::{ExecutableTransaction, InstructionV2};
28use sbor::prelude::ToString;
29
30/// Multi-thread intent processor for executing multiple subintents
31pub struct MultiThreadIntentProcessor<'e> {
32    pub threads: Vec<(IntentProcessor<'e, InstructionV2>, Vec<usize>)>,
33}
34
35impl<'e> MultiThreadIntentProcessor<'e> {
36    pub fn init<Y: SystemBasedKernelApi>(
37        executable: &'e ExecutableTransaction,
38        global_address_reservations: &[GlobalAddressReservation],
39        api: &mut Y,
40    ) -> Result<Self, RuntimeError> {
41        let mut txn_processors = vec![];
42
43        // Setup
44        for (thread_id, intent) in executable.all_intents().enumerate() {
45            api.kernel_switch_stack(thread_id)?;
46
47            // We create the auth zone for the transaction processor.
48            // This needs a `SystemService` to resolve the current actor (Root) to get the global caller (None),
49            // and also to create the node and substates.
50            let mut system_service = SystemService::new(api);
51            let simulate_every_proof_under_resources = intent
52                .auth_zone_init
53                .simulate_every_proof_under_resources
54                .clone();
55            let initial_non_fungible_id_proofs =
56                intent.auth_zone_init.initial_non_fungible_id_proofs.clone();
57            let auth_zone = AuthModule::create_auth_zone(
58                &mut system_service,
59                None,
60                simulate_every_proof_under_resources,
61                initial_non_fungible_id_proofs,
62            )?;
63
64            api.kernel_set_call_frame_data(Actor::Function(FunctionActor {
65                blueprint_id: BlueprintId::new(
66                    &TRANSACTION_PROCESSOR_PACKAGE,
67                    TRANSACTION_PROCESSOR_BLUEPRINT,
68                ),
69                ident: TRANSACTION_PROCESSOR_RUN_IDENT.to_string(),
70                auth_zone,
71            }))?;
72
73            let mut system_service = SystemService::new(api);
74            let txn_processor = IntentProcessor::<InstructionV2>::init(
75                intent.encoded_instructions.as_ref(),
76                global_address_reservations,
77                &intent.blobs,
78                MAX_TOTAL_BLOB_SIZE_PER_INVOCATION,
79                &mut system_service,
80            )?;
81
82            txn_processors.push((
83                txn_processor,
84                intent
85                    .children_subintent_indices
86                    .iter()
87                    .map(|index| index.0 + 1)
88                    .collect::<Vec<_>>(),
89            ));
90        }
91        Ok(Self {
92            threads: txn_processors,
93        })
94    }
95
96    pub fn execute<Y: SystemBasedKernelApi>(&mut self, api: &mut Y) -> Result<(), RuntimeError> {
97        let mut cur_thread = 0;
98        let mut parent_stack = vec![];
99        let mut passed_value = None;
100
101        enum PostExecution {
102            SwitchThread(usize, IndexedScryptoValue, bool),
103            VerifyParent(AccessRule),
104            RootIntentDone,
105        }
106
107        loop {
108            api.kernel_switch_stack(cur_thread)?;
109            let (txn_thread, children_mapping) = self.threads.get_mut(cur_thread).unwrap();
110
111            let mut system_service = SystemService::new(api);
112            let post_exec = match txn_thread.resume(passed_value.take(), &mut system_service)? {
113                ResumeResult::YieldToChild(child, value) => {
114                    let child = *children_mapping
115                        .get(child)
116                        .ok_or(RuntimeError::SystemError(SystemError::IntentError(
117                            IntentError::InvalidIntentIndex(child),
118                        )))?;
119                    parent_stack.push(cur_thread);
120                    PostExecution::SwitchThread(child, value, false)
121                }
122                ResumeResult::YieldToParent(value) => {
123                    let parent = parent_stack.pop().ok_or(RuntimeError::SystemError(
124                        SystemError::IntentError(IntentError::NoParentToYieldTo),
125                    ))?;
126                    PostExecution::SwitchThread(parent, value, false)
127                }
128                ResumeResult::VerifyParent(rule) => PostExecution::VerifyParent(rule),
129                ResumeResult::DoneAndYieldToParent(value) => {
130                    let parent = parent_stack.pop().ok_or(RuntimeError::SystemError(
131                        SystemError::IntentError(IntentError::NoParentToYieldTo),
132                    ))?;
133                    PostExecution::SwitchThread(parent, value, true)
134                }
135                ResumeResult::Done => PostExecution::RootIntentDone,
136            };
137
138            match post_exec {
139                PostExecution::SwitchThread(next_thread, value, intent_done) => {
140                    // Checked passed values
141                    Self::check_yielded_value(&value, api)?;
142                    api.kernel_send_to_stack(next_thread, &value)?;
143                    passed_value = Some(value);
144
145                    // Cleanup stack if intent is done. This must be done after the above kernel_send_to_stack.
146                    if intent_done {
147                        Self::cleanup_stack(api)?;
148                    }
149
150                    cur_thread = next_thread;
151                }
152                PostExecution::VerifyParent(rule) => {
153                    let save_cur_thread = cur_thread;
154                    let system_version = api.system_service().system().versioned_system_logic;
155
156                    let parent = if system_version.use_root_for_verify_parent_instruction() {
157                        parent_stack.first()
158                    } else {
159                        // As of CuttlefishPart2, we use the direct parent intent for the VERIFY_PARENT instruction
160                        parent_stack.last()
161                    };
162                    let parent_thread_index = parent.cloned().ok_or(RuntimeError::SystemError(
163                        SystemError::IntentError(IntentError::CannotVerifyParentOnRoot),
164                    ))?;
165                    api.kernel_switch_stack(parent_thread_index)?;
166
167                    // Create a temporary authzone with the current authzone as the global caller since
168                    // check_authorization_against_access_rule tests against the global caller authzones.
169                    // Run assert_access_rule against this authzone
170                    {
171                        let auth_zone = Self::create_temp_child_auth_zone_for_verify_parent(api)?;
172                        let mut system_service = SystemService::new(api);
173                        let auth_result = Authorization::check_authorization_against_access_rule(
174                            &mut system_service,
175                            &auth_zone,
176                            &rule,
177                        )?;
178                        match auth_result {
179                            AuthorizationCheckResult::Authorized => {}
180                            AuthorizationCheckResult::Failed(..) => {
181                                return Err(RuntimeError::SystemError(SystemError::IntentError(
182                                    IntentError::VerifyParentFailed,
183                                )))
184                            }
185                        }
186                        api.kernel_drop_node(&auth_zone)?;
187                    }
188
189                    api.kernel_switch_stack(save_cur_thread)?;
190                }
191                PostExecution::RootIntentDone => {
192                    Self::cleanup_stack(api)?;
193                    break;
194                }
195            }
196        }
197
198        assert!(parent_stack.is_empty());
199
200        Ok(())
201    }
202
203    fn create_temp_child_auth_zone_for_verify_parent<Y: SystemBasedKernelApi>(
204        api: &mut Y,
205    ) -> Result<NodeId, RuntimeError> {
206        let actor = api.kernel_get_system_state().current_call_frame;
207        let auth_zone = actor.self_auth_zone().unwrap();
208        let blueprint_id = actor.blueprint_id().unwrap();
209        let auth_zone = AuthZone::new(
210            vec![],
211            Default::default(),
212            Default::default(),
213            None,
214            Some((
215                GlobalCaller::PackageBlueprint(blueprint_id),
216                Reference(auth_zone),
217            )),
218            None,
219        );
220
221        let new_auth_zone = api.kernel_allocate_node_id(EntityType::InternalGenericComponent)?;
222
223        api.kernel_create_node(
224            new_auth_zone,
225            btreemap!(
226                MAIN_BASE_PARTITION => btreemap!(
227                    AuthZoneField::AuthZone.into() => IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(auth_zone))
228                ),
229                TYPE_INFO_FIELD_PARTITION => type_info_partition(TypeInfoSubstate::Object(ObjectInfo {
230                    blueprint_info: BlueprintInfo {
231                        blueprint_id: BlueprintId::new(&RESOURCE_PACKAGE, AUTH_ZONE_BLUEPRINT),
232                        blueprint_version: BlueprintVersion::default(),
233                        outer_obj_info: OuterObjectInfo::default(),
234                        features: indexset!(),
235                        generic_substitutions: vec![],
236                    },
237                    object_type: ObjectType::Owned,
238                }))
239            ),
240        )?;
241        api.kernel_pin_node(new_auth_zone)?;
242
243        Ok(new_auth_zone)
244    }
245
246    fn check_yielded_value<Y: SystemBasedKernelApi>(
247        value: &IndexedScryptoValue,
248        api: &mut Y,
249    ) -> Result<(), RuntimeError> {
250        let mut system_service = SystemService::new(api);
251        for node_id in value.owned_nodes() {
252            let object_info: ObjectInfo = system_service.get_object_info(node_id)?;
253
254            let blueprint_id = object_info.blueprint_info.blueprint_id;
255            if let (RESOURCE_PACKAGE, FUNGIBLE_PROOF_BLUEPRINT | NON_FUNGIBLE_PROOF_BLUEPRINT) = (
256                blueprint_id.package_address,
257                blueprint_id.blueprint_name.as_str(),
258            ) {
259                return Err(RuntimeError::SystemError(SystemError::IntentError(
260                    IntentError::CannotYieldProof,
261                )));
262            }
263        }
264
265        Ok(())
266    }
267
268    fn cleanup_stack<Y: SystemBasedKernelApi>(api: &mut Y) -> Result<(), RuntimeError> {
269        let owned_nodes = api.kernel_get_owned_nodes()?;
270        System::auto_drop(owned_nodes, api)?;
271
272        let actor = api.kernel_get_system_state().current_call_frame;
273        match actor {
274            Actor::Function(FunctionActor { auth_zone, .. }) => {
275                let auth_zone = *auth_zone;
276                let mut system_service = SystemService::new(api);
277                AuthModule::teardown_auth_zone(&mut system_service, auth_zone)?;
278            }
279            _ => {
280                panic!("unexpected");
281            }
282        }
283        let owned_nodes = api.kernel_get_owned_nodes()?;
284        if !owned_nodes.is_empty() {
285            return Err(RuntimeError::KernelError(KernelError::OrphanedNodes(
286                owned_nodes
287                    .into_iter()
288                    .map(|node_id| node_id.into())
289                    .collect(),
290            )));
291        }
292
293        Ok(())
294    }
295}