Skip to main content

miden_tx/executor/
exec_host.rs

1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_processor::advice::AdviceMutation;
7use miden_processor::event::EventError;
8use miden_processor::mast::MastForest;
9use miden_processor::{BaseHost, FutureMaybeSend, Host, ProcessorState};
10use miden_protocol::account::auth::PublicKeyCommitment;
11use miden_protocol::account::{
12    AccountCode,
13    AccountDelta,
14    AccountId,
15    PartialAccount,
16    StorageMapKey,
17    StorageSlotId,
18    StorageSlotName,
19};
20use miden_protocol::assembly::debuginfo::Location;
21use miden_protocol::assembly::{SourceFile, SourceManagerSync, SourceSpan};
22use miden_protocol::asset::{AssetVaultKey, AssetWitness, FungibleAsset};
23use miden_protocol::block::BlockNumber;
24use miden_protocol::crypto::merkle::smt::SmtProof;
25use miden_protocol::note::{
26    NoteRecipient,
27    NoteScript,
28    NoteScriptRoot,
29    NoteStorage,
30    PartialNoteMetadata,
31};
32use miden_protocol::transaction::{
33    InputNote,
34    InputNotes,
35    RawOutputNote,
36    TransactionAdviceInputs,
37    TransactionSummary,
38};
39use miden_protocol::vm::{AdviceMap, EventId, EventName};
40use miden_protocol::{Felt, Hasher, Word};
41use miden_standards::note::StandardNote;
42
43use crate::auth::{SigningInputs, TransactionAuthenticator};
44use crate::errors::TransactionKernelError;
45use crate::host::{
46    RecipientData,
47    ScriptMastForestStore,
48    TransactionBaseHost,
49    TransactionEvent,
50    TransactionProgress,
51    TransactionProgressEvent,
52};
53use crate::{AccountProcedureIndexMap, DataStore};
54
55// TRANSACTION EXECUTOR HOST
56// ================================================================================================
57
58/// The transaction executor host is responsible for handling [`FutureMaybeSend`] requests made by
59/// the transaction kernel during execution. In particular, it responds to signature generation
60/// requests by forwarding the request to the contained [`TransactionAuthenticator`].
61///
62/// Transaction hosts are created on a per-transaction basis. That is, a transaction host is meant
63/// to support execution of a single transaction and is discarded after the transaction finishes
64/// execution.
65pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
66where
67    STORE: DataStore,
68    AUTH: TransactionAuthenticator,
69{
70    /// The underlying base transaction host.
71    base_host: TransactionBaseHost<'store, STORE>,
72
73    /// Tracks the number of cycles for each of the transaction execution stages.
74    ///
75    /// The progress is updated event handlers.
76    tx_progress: TransactionProgress,
77
78    /// Serves signature generation requests from the transaction runtime for signatures which are
79    /// not present in the `generated_signatures` field.
80    authenticator: Option<&'auth AUTH>,
81
82    /// The reference block of the transaction.
83    ref_block: BlockNumber,
84
85    /// The foreign account code that was lazy loaded during transaction execution.
86    ///
87    /// This is required for re-executing the transaction, e.g. as part of transaction proving.
88    accessed_foreign_account_code: Vec<AccountCode>,
89
90    /// Storage slot names for foreign accounts accessed during transaction execution.
91    foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
92
93    /// Contains generated signatures (as a message |-> signature map) required for transaction
94    /// execution. Once a signature was created for a given message, it is inserted into this map.
95    /// After transaction execution, these can be inserted into the advice inputs to re-execute the
96    /// transaction without having to regenerate the signature or requiring access to the
97    /// authenticator that produced it.
98    generated_signatures: BTreeMap<Word, Vec<Felt>>,
99
100    /// The initial balance of the fee asset in the native account's vault.
101    initial_fee_asset_balance: u64,
102
103    /// The source manager to track source code file span information, improving any MASM related
104    /// error messages.
105    source_manager: Arc<dyn SourceManagerSync>,
106}
107
108impl<'store, 'auth, STORE, AUTH> TransactionExecutorHost<'store, 'auth, STORE, AUTH>
109where
110    STORE: DataStore + Sync,
111    AUTH: TransactionAuthenticator + Sync,
112{
113    // CONSTRUCTORS
114    // --------------------------------------------------------------------------------------------
115
116    /// Creates a new [`TransactionExecutorHost`] instance from the provided inputs.
117    #[allow(clippy::too_many_arguments)]
118    pub fn new(
119        account: &PartialAccount,
120        input_notes: InputNotes<InputNote>,
121        mast_store: &'store STORE,
122        scripts_mast_store: ScriptMastForestStore,
123        acct_procedure_index_map: AccountProcedureIndexMap,
124        authenticator: Option<&'auth AUTH>,
125        ref_block: BlockNumber,
126        initial_fee_asset_balance: u64,
127        source_manager: Arc<dyn SourceManagerSync>,
128    ) -> Self {
129        let base_host = TransactionBaseHost::new(
130            account,
131            input_notes,
132            mast_store,
133            scripts_mast_store,
134            acct_procedure_index_map,
135        );
136
137        Self {
138            base_host,
139            tx_progress: TransactionProgress::default(),
140            authenticator,
141            ref_block,
142            accessed_foreign_account_code: Vec::new(),
143            foreign_account_slot_names: BTreeMap::new(),
144            generated_signatures: BTreeMap::new(),
145            initial_fee_asset_balance,
146            source_manager,
147        }
148    }
149
150    // PUBLIC ACCESSORS
151    // --------------------------------------------------------------------------------------------
152
153    /// Returns a reference to the `tx_progress` field of this transaction host.
154    pub fn tx_progress(&self) -> &TransactionProgress {
155        &self.tx_progress
156    }
157
158    /// Returns a reference to the foreign account slot names collected during execution.
159    pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
160        &self.foreign_account_slot_names
161    }
162
163    // EVENT HANDLERS
164    // --------------------------------------------------------------------------------------------
165
166    /// Handles a request for a foreign account by querying the data store for its account inputs.
167    async fn on_foreign_account_requested(
168        &mut self,
169        foreign_account_id: AccountId,
170    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
171        let foreign_account_inputs = self
172            .base_host
173            .store()
174            .get_foreign_account_inputs(foreign_account_id, self.ref_block)
175            .await
176            .map_err(|err| TransactionKernelError::GetForeignAccountInputs {
177                foreign_account_id,
178                ref_block: self.ref_block,
179                source: err,
180            })?;
181
182        let mut tx_advice_inputs = TransactionAdviceInputs::default();
183        tx_advice_inputs.add_foreign_accounts([&foreign_account_inputs]);
184
185        // Extract and store slot names for this foreign account and store.
186        foreign_account_inputs.storage().header().slots().for_each(|slot| {
187            self.foreign_account_slot_names.insert(slot.id(), slot.name().clone());
188        });
189
190        self.base_host.load_foreign_account_code(foreign_account_inputs.code());
191
192        // Add the foreign account's code to the list of accessed code.
193        self.accessed_foreign_account_code.push(foreign_account_inputs.code().clone());
194
195        Ok(tx_advice_inputs.into_advice_mutations().collect())
196    }
197
198    /// Pushes a signature to the advice stack as a response to the `AuthRequest` event.
199    ///
200    /// The signature is requested from the host's authenticator.
201    pub async fn on_auth_requested(
202        &mut self,
203        pub_key_hash: Word,
204        tx_summary: TransactionSummary,
205    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
206        let signing_inputs = SigningInputs::TransactionSummary(Box::new(tx_summary));
207
208        let authenticator =
209            self.authenticator.ok_or(TransactionKernelError::MissingAuthenticator)?;
210
211        // get the message that will be signed by the authenticator
212        let message = signing_inputs.to_commitment();
213
214        let signature: Vec<Felt> = authenticator
215            .get_signature(PublicKeyCommitment::from(pub_key_hash), &signing_inputs)
216            .await
217            .map_err(TransactionKernelError::SignatureGenerationFailed)?
218            .to_prepared_signature(message);
219
220        let signature_key = Hasher::merge(&[pub_key_hash, message]);
221        self.generated_signatures.insert(signature_key, signature.clone());
222
223        Ok(vec![AdviceMutation::extend_stack(signature)])
224    }
225
226    /// Handles the [`TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount`] and returns an error
227    /// if the account cannot pay the fee.
228    async fn on_before_tx_fee_removed_from_account(
229        &self,
230        fee_asset: FungibleAsset,
231    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
232        // Construct initial fee asset.
233        let initial_fee_asset =
234            FungibleAsset::new(fee_asset.faucet_id(), self.initial_fee_asset_balance)
235                .expect("fungible asset created from fee asset should be valid");
236
237        // Compute the current balance of the fee asset in the account based on the initial value
238        // and the delta.
239        let current_fee_asset = {
240            let fee_asset_amount_delta = self
241                .base_host
242                .account_delta_tracker()
243                .vault_delta()
244                .fungible()
245                .amount(&initial_fee_asset.vault_key())
246                .unwrap_or(0);
247
248            // SAFETY: Initial fee faucet ID should be a fungible faucet and amount should
249            // be less than MAX_AMOUNT as checked by the account delta.
250            let fee_asset_delta = FungibleAsset::new(
251                initial_fee_asset.faucet_id(),
252                fee_asset_amount_delta.unsigned_abs(),
253            )
254            .expect("faucet ID and amount should be valid");
255
256            // SAFETY: These computations are essentially the same as the ones executed by the
257            // transaction kernel, which should have aborted if they weren't valid.
258            if fee_asset_amount_delta > 0 {
259                initial_fee_asset
260                    .add(fee_asset_delta)
261                    .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT")
262            } else {
263                initial_fee_asset
264                    .sub(fee_asset_delta)
265                    .expect("transaction kernel should ensure amount is not negative")
266            }
267        };
268
269        // Return an error if the balance in the account does not cover the fee.
270        if current_fee_asset.amount() < fee_asset.amount() {
271            return Err(TransactionKernelError::InsufficientFee {
272                account_balance: current_fee_asset.amount().as_u64(),
273                tx_fee: fee_asset.amount().as_u64(),
274            });
275        }
276
277        Ok(Vec::new())
278    }
279
280    /// Handles a request for a storage map witness by querying the data store for a merkle path.
281    ///
282    /// Note that we request witnesses against the _initial_ map root of the accounts. See also
283    /// [`Self::on_account_vault_asset_witness_requested`] for more on this topic.
284    async fn on_account_storage_map_witness_requested(
285        &self,
286        active_account_id: AccountId,
287        map_root: Word,
288        map_key: StorageMapKey,
289    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
290        let storage_map_witness = self
291            .base_host
292            .store()
293            .get_storage_map_witness(active_account_id, map_root, map_key)
294            .await
295            .map_err(|err| TransactionKernelError::GetStorageMapWitness {
296                map_root,
297                map_key,
298                source: err,
299            })?;
300
301        // Get the nodes in the proof and insert them into the merkle store.
302        let merkle_store_ext =
303            AdviceMutation::extend_merkle_store(storage_map_witness.authenticated_nodes());
304
305        let smt_proof = SmtProof::from(storage_map_witness);
306        let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
307            smt_proof.leaf().hash(),
308            smt_proof.leaf().to_elements().collect::<Vec<_>>(),
309        )]));
310
311        Ok(vec![merkle_store_ext, map_ext])
312    }
313
314    /// Handles a request to an asset witness by querying the data store for a merkle path.
315    ///
316    /// ## Native Account
317    ///
318    /// For the native account we always request witnesses for the initial vault root, because the
319    /// data store only has the state of the account vault at the beginning of the transaction.
320    /// Since the vault root can change as the transaction progresses, this means the witnesses
321    /// may become _partially_ or fully outdated. To see why they can only be _partially_ outdated,
322    /// consider the following example:
323    ///
324    /// ```text
325    ///      A               A'
326    ///     / \             /  \
327    ///    B   C    ->    B'    C
328    ///   / \  / \       /  \  / \
329    ///  D  E F   G     D   E' F  G
330    /// ```
331    ///
332    /// Leaf E was updated to E', in turn updating nodes B and A. If we now request the merkle path
333    /// to G against root A (the initial vault root), we'll get nodes F and B. F is a node in the
334    /// updated tree, while B is not. We insert both into the merkle store anyway. Now, if the
335    /// transaction attempts to verify the merkle path to G, it can do so because F and B' are in
336    /// the merkle store. Note that B' is in the store because the transaction inserted it into the
337    /// merkle store as part of updating E, not because we inserted it. B is present in the store,
338    /// but is simply ignored for the purpose of verifying G's inclusion.
339    ///
340    /// ## Foreign Accounts
341    ///
342    /// Foreign accounts are read-only and so they cannot change throughout transaction execution.
343    /// This means their _current_ vault root is always equivalent to their _initial_ vault root.
344    /// So, for foreign accounts, just like for the native account, we also always request
345    /// witnesses for the initial vault root.
346    async fn on_account_vault_asset_witness_requested(
347        &self,
348        active_account_id: AccountId,
349        vault_root: Word,
350        asset_key: AssetVaultKey,
351    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
352        let asset_witnesses = self
353            .base_host
354            .store()
355            .get_vault_asset_witnesses(
356                active_account_id,
357                vault_root,
358                BTreeSet::from_iter([asset_key]),
359            )
360            .await
361            .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
362                vault_root,
363                asset_key,
364                source: err,
365            })?;
366
367        Ok(asset_witnesses.into_iter().flat_map(asset_witness_to_advice_mutation).collect())
368    }
369
370    /// Handles a request for a [`NoteScript`] during transaction execution when the script is not
371    /// already in the advice provider.
372    ///
373    /// Standard note scripts (P2ID, etc.) are resolved directly from [`StandardNote`], avoiding a
374    /// data store round-trip. Non-standard scripts are fetched from the [`DataStore`].
375    ///
376    /// The resolved script is used to build a [`NoteRecipient`], which is then used to create
377    /// an [`OutputNoteBuilder`]. This function is only called for notes where the script is not
378    /// already in the advice provider.
379    ///
380    /// # Errors
381    /// Returns an error if:
382    /// - The note is public and the script is not found in the data store.
383    /// - Constructing the recipient with the fetched script does not match the expected recipient
384    ///   digest.
385    /// - The data store returns an error when fetching the script.
386    async fn on_note_script_requested(
387        &mut self,
388        note_idx: usize,
389        recipient_digest: Word,
390        script_root: Word,
391        metadata: PartialNoteMetadata,
392        note_storage: NoteStorage,
393        serial_num: Word,
394    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
395        // Resolve standard note scripts directly, avoiding a data store round-trip.
396        let script_root = NoteScriptRoot::from_raw(script_root);
397        let note_script: Option<NoteScript> =
398            if let Some(standard_note) = StandardNote::from_script_root(script_root) {
399                Some(standard_note.script())
400            } else {
401                self.base_host.store().get_note_script(script_root).await.map_err(|err| {
402                    TransactionKernelError::other_with_source(
403                        "failed to retrieve note script from data store",
404                        err,
405                    )
406                })?
407            };
408
409        match note_script {
410            Some(note_script) => {
411                let script_felts: Vec<Felt> = (&note_script).into();
412                let recipient = NoteRecipient::new(serial_num, note_script, note_storage);
413
414                if recipient.digest() != recipient_digest {
415                    return Err(TransactionKernelError::other(format!(
416                        "recipient digest is {recipient_digest}, but recipient constructed from raw inputs has digest {}",
417                        recipient.digest()
418                    )));
419                }
420
421                self.base_host.output_note_from_recipient(note_idx, metadata, recipient)?;
422
423                Ok(vec![AdviceMutation::extend_map(AdviceMap::from_iter([(
424                    Word::from(script_root),
425                    script_felts,
426                )]))])
427            },
428            None if metadata.is_private() => {
429                self.base_host.output_note_from_recipient_digest(
430                    note_idx,
431                    metadata,
432                    recipient_digest,
433                )?;
434
435                Ok(Vec::new())
436            },
437            None => Err(TransactionKernelError::other(format!(
438                "note script with root {} not found in data store for public note",
439                Word::from(script_root),
440            ))),
441        }
442    }
443
444    /// Consumes `self` and returns the account delta, output notes, generated signatures and
445    /// transaction progress.
446    #[allow(clippy::type_complexity)]
447    pub fn into_parts(
448        self,
449    ) -> (
450        AccountDelta,
451        InputNotes<InputNote>,
452        Vec<RawOutputNote>,
453        Vec<AccountCode>,
454        BTreeMap<Word, Vec<Felt>>,
455        TransactionProgress,
456        BTreeMap<StorageSlotId, StorageSlotName>,
457    ) {
458        let (account_delta, input_notes, output_notes) = self.base_host.into_parts();
459
460        (
461            account_delta,
462            input_notes,
463            output_notes,
464            self.accessed_foreign_account_code,
465            self.generated_signatures,
466            self.tx_progress,
467            self.foreign_account_slot_names,
468        )
469    }
470}
471
472// HOST IMPLEMENTATION
473// ================================================================================================
474
475impl<STORE, AUTH> BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
476where
477    STORE: DataStore + Sync,
478    AUTH: TransactionAuthenticator + Sync,
479{
480    fn get_label_and_source_file(
481        &self,
482        location: &Location,
483    ) -> (SourceSpan, Option<Arc<SourceFile>>) {
484        let source_manager = self.source_manager.as_ref();
485        let maybe_file = source_manager.get_by_uri(location.uri());
486        let span = source_manager.location_to_span(location.clone()).unwrap_or_default();
487        (span, maybe_file)
488    }
489
490    fn resolve_event(&self, event_id: EventId) -> Option<&EventName> {
491        self.base_host.resolve_event(event_id)
492    }
493}
494
495impl<STORE, AUTH> Host for TransactionExecutorHost<'_, '_, STORE, AUTH>
496where
497    STORE: DataStore + Sync,
498    AUTH: TransactionAuthenticator + Sync,
499{
500    fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
501        let mast_forest = self.base_host.get_mast_forest(node_digest);
502        async move { mast_forest }
503    }
504
505    fn on_event(
506        &mut self,
507        process: &ProcessorState,
508    ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
509        let core_lib_event_result = self.base_host.handle_core_lib_events(process);
510
511        // If the event was handled by a core lib handler (Ok(Some)), we will return the result from
512        // within the async block below. So, we only need to extract th tx event if the event was
513        // not yet handled (Ok(None)).
514        let tx_event_result = match core_lib_event_result {
515            Ok(None) => Some(TransactionEvent::extract(&self.base_host, process)),
516            _ => None,
517        };
518
519        async move {
520            if let Some(mutations) = core_lib_event_result? {
521                return Ok(mutations);
522            }
523
524            // The outer None means the event was handled by core lib handlers.
525            let Some(tx_event_result) = tx_event_result else {
526                return Ok(Vec::new());
527            };
528            // The inner None means the transaction event ID does not need to be handled.
529            let Some(tx_event) = tx_event_result? else {
530                return Ok(Vec::new());
531            };
532
533            let result = match tx_event {
534                TransactionEvent::AccountBeforeForeignLoad { foreign_account_id: account_id } => {
535                    self.on_foreign_account_requested(account_id).await
536                },
537
538                TransactionEvent::AccountVaultAfterRemoveAsset { asset } => {
539                    self.base_host.on_account_vault_after_remove_asset(asset)
540                },
541                TransactionEvent::AccountVaultAfterAddAsset { asset } => {
542                    self.base_host.on_account_vault_after_add_asset(asset)
543                },
544
545                TransactionEvent::AccountStorageAfterSetItem { slot_name, new_value } => {
546                    self.base_host.on_account_storage_after_set_item(slot_name, new_value)
547                },
548
549                TransactionEvent::AccountStorageAfterSetMapItem {
550                    slot_name,
551                    key,
552                    old_value: prev_map_value,
553                    new_value,
554                } => self.base_host.on_account_storage_after_set_map_item(
555                    slot_name,
556                    key,
557                    prev_map_value,
558                    new_value,
559                ),
560
561                TransactionEvent::AccountVaultBeforeAssetAccess {
562                    active_account_id,
563                    vault_root,
564                    asset_key,
565                } => {
566                    self.on_account_vault_asset_witness_requested(
567                        active_account_id,
568                        vault_root,
569                        asset_key,
570                    )
571                    .await
572                },
573
574                TransactionEvent::AccountStorageBeforeMapItemAccess {
575                    active_account_id,
576                    map_root,
577                    map_key,
578                } => {
579                    self.on_account_storage_map_witness_requested(
580                        active_account_id,
581                        map_root,
582                        map_key,
583                    )
584                    .await
585                },
586
587                TransactionEvent::AccountAfterIncrementNonce => {
588                    self.base_host.on_account_after_increment_nonce()
589                },
590
591                TransactionEvent::AccountPushProcedureIndex { code_commitment, procedure_root } => {
592                    self.base_host.on_account_push_procedure_index(code_commitment, procedure_root)
593                },
594
595                TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => {
596                    match recipient_data {
597                        RecipientData::Digest(recipient_digest) => {
598                            self.base_host.output_note_from_recipient_digest(
599                                note_idx,
600                                metadata,
601                                recipient_digest,
602                            )
603                        },
604                        RecipientData::Recipient(note_recipient) => self
605                            .base_host
606                            .output_note_from_recipient(note_idx, metadata, note_recipient),
607                        RecipientData::ScriptMissing {
608                            recipient_digest,
609                            serial_num,
610                            script_root,
611                            note_storage,
612                        } => {
613                            self.on_note_script_requested(
614                                note_idx,
615                                recipient_digest,
616                                script_root,
617                                metadata,
618                                note_storage,
619                                serial_num,
620                            )
621                            .await
622                        },
623                    }
624                },
625
626                TransactionEvent::NoteBeforeAddAsset { note_idx, asset } => {
627                    self.base_host.on_note_before_add_asset(note_idx, asset)
628                },
629
630                TransactionEvent::NoteBeforeAddAttachment { note_idx, attachment } => self
631                    .base_host
632                    .on_note_before_add_attachment(note_idx, attachment)
633                    .map(|_| Vec::new()),
634
635                TransactionEvent::AuthRequest { pub_key_hash, tx_summary, signature } => {
636                    if let Some(signature) = signature {
637                        Ok(self.base_host.on_auth_requested(signature))
638                    } else {
639                        self.on_auth_requested(pub_key_hash, tx_summary).await
640                    }
641                },
642
643                // This always returns an error to abort the transaction.
644                TransactionEvent::Unauthorized { tx_summary } => {
645                    Err(TransactionKernelError::Unauthorized(Box::new(tx_summary)))
646                },
647
648                TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount { fee_asset } => {
649                    self.on_before_tx_fee_removed_from_account(fee_asset).await
650                },
651
652                TransactionEvent::LinkMapSet { advice_mutation } => Ok(advice_mutation),
653                TransactionEvent::LinkMapGet { advice_mutation } => Ok(advice_mutation),
654                TransactionEvent::Progress(tx_progress) => match tx_progress {
655                    TransactionProgressEvent::PrologueStart(clk) => {
656                        self.tx_progress.start_prologue(clk);
657                        Ok(Vec::new())
658                    },
659                    TransactionProgressEvent::PrologueEnd(clk) => {
660                        self.tx_progress.end_prologue(clk);
661                        Ok(Vec::new())
662                    },
663                    TransactionProgressEvent::NotesProcessingStart(clk) => {
664                        self.tx_progress.start_notes_processing(clk);
665                        Ok(Vec::new())
666                    },
667                    TransactionProgressEvent::NotesProcessingEnd(clk) => {
668                        self.tx_progress.end_notes_processing(clk);
669                        Ok(Vec::new())
670                    },
671                    TransactionProgressEvent::NoteExecutionStart { note_id, clk } => {
672                        self.tx_progress.start_note_execution(clk, note_id);
673                        Ok(Vec::new())
674                    },
675                    TransactionProgressEvent::NoteExecutionEnd(clk) => {
676                        self.tx_progress.end_note_execution(clk);
677                        Ok(Vec::new())
678                    },
679                    TransactionProgressEvent::TxScriptProcessingStart(clk) => {
680                        self.tx_progress.start_tx_script_processing(clk);
681                        Ok(Vec::new())
682                    },
683                    TransactionProgressEvent::TxScriptProcessingEnd(clk) => {
684                        self.tx_progress.end_tx_script_processing(clk);
685                        Ok(Vec::new())
686                    },
687                    TransactionProgressEvent::EpilogueStart(clk) => {
688                        self.tx_progress.start_epilogue(clk);
689                        Ok(Vec::new())
690                    },
691                    TransactionProgressEvent::EpilogueEnd(clk) => {
692                        self.tx_progress.end_epilogue(clk);
693                        Ok(Vec::new())
694                    },
695                    TransactionProgressEvent::EpilogueAuthProcStart(clk) => {
696                        self.tx_progress.start_auth_procedure(clk);
697                        Ok(Vec::new())
698                    },
699                    TransactionProgressEvent::EpilogueAuthProcEnd(clk) => {
700                        self.tx_progress.end_auth_procedure(clk);
701                        Ok(Vec::new())
702                    },
703                    TransactionProgressEvent::EpilogueAfterTxCyclesObtained(clk) => {
704                        self.tx_progress.epilogue_after_tx_cycles_obtained(clk);
705                        Ok(Vec::new())
706                    },
707                },
708            };
709
710            result.map_err(EventError::from)
711        }
712    }
713}
714
715// HELPER FUNCTIONS
716// ================================================================================================
717
718/// Converts an [`AssetWitness`] into the set of advice mutations that need to be inserted in order
719/// to access the asset.
720fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> [AdviceMutation; 2] {
721    // Get the nodes in the proof and insert them into the merkle store.
722    let merkle_store_ext = AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes());
723
724    let smt_proof = SmtProof::from(asset_witness);
725    let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
726        smt_proof.leaf().hash(),
727        smt_proof.leaf().to_elements().collect::<Vec<_>>(),
728    )]));
729
730    [merkle_store_ext, map_ext]
731}