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