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