miden_tx/executor/
exec_host.rs

1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_lib::errors::TransactionKernelError;
7use miden_lib::transaction::TransactionEvent;
8use miden_objects::account::{AccountDelta, PartialAccount};
9use miden_objects::assembly::debuginfo::Location;
10use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan};
11use miden_objects::asset::FungibleAsset;
12use miden_objects::block::FeeParameters;
13use miden_objects::transaction::{InputNote, InputNotes, OutputNote};
14use miden_objects::{Felt, Hasher, Word};
15use miden_processor::{
16    AdviceMutation,
17    AsyncHost,
18    BaseHost,
19    EventError,
20    FutureMaybeSend,
21    MastForest,
22    MastForestStore,
23    ProcessState,
24};
25
26use crate::AccountProcedureIndexMap;
27use crate::auth::{SigningInputs, TransactionAuthenticator};
28use crate::host::{
29    ScriptMastForestStore,
30    TransactionBaseHost,
31    TransactionEventData,
32    TransactionEventHandling,
33    TransactionProgress,
34};
35
36// TRANSACTION EXECUTOR HOST
37// ================================================================================================
38
39/// The transaction executor host is responsible for handling [`FutureMaybeSend`] requests made by
40/// the transaction kernel during execution. In particular, it responds to signature generation
41/// requests by forwarding the request to the contained [`TransactionAuthenticator`].
42///
43/// Transaction hosts are created on a per-transaction basis. That is, a transaction host is meant
44/// to support execution of a single transaction and is discarded after the transaction finishes
45/// execution.
46pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
47where
48    STORE: MastForestStore,
49    AUTH: TransactionAuthenticator,
50{
51    /// The underlying base transaction host.
52    base_host: TransactionBaseHost<'store, STORE>,
53
54    /// Serves signature generation requests from the transaction runtime for signatures which are
55    /// not present in the `generated_signatures` field.
56    authenticator: Option<&'auth AUTH>,
57
58    /// Contains generated signatures (as a message |-> signature map) required for transaction
59    /// execution. Once a signature was created for a given message, it is inserted into this map.
60    /// After transaction execution, these can be inserted into the advice inputs to re-execute the
61    /// transaction without having to regenerate the signature or requiring access to the
62    /// authenticator that produced it.
63    generated_signatures: BTreeMap<Word, Vec<Felt>>,
64
65    /// The balance of the native asset in the account at the beginning of transaction execution.
66    initial_native_asset: FungibleAsset,
67
68    /// The source manager to track source code file span information, improving any MASM related
69    /// error messages.
70    source_manager: Arc<dyn SourceManagerSync>,
71}
72
73impl<'store, 'auth, STORE, AUTH> TransactionExecutorHost<'store, 'auth, STORE, AUTH>
74where
75    STORE: MastForestStore + Sync,
76    AUTH: TransactionAuthenticator + Sync,
77{
78    // CONSTRUCTORS
79    // --------------------------------------------------------------------------------------------
80
81    /// Creates a new [`TransactionExecutorHost`] instance from the provided inputs.
82    pub fn new(
83        account: &PartialAccount,
84        input_notes: InputNotes<InputNote>,
85        mast_store: &'store STORE,
86        scripts_mast_store: ScriptMastForestStore,
87        acct_procedure_index_map: AccountProcedureIndexMap,
88        authenticator: Option<&'auth AUTH>,
89        fee_parameters: &FeeParameters,
90        source_manager: Arc<dyn SourceManagerSync>,
91    ) -> Self {
92        // TODO: Once we have lazy account loading, this should be loaded in on_tx_fee_computed to
93        // avoid the use of PartialVault entirely, which in the future, may or may not track
94        // all assets in the account at this point. Here we assume it does track _all_ assets of the
95        // account.
96        let initial_native_asset = {
97            let native_asset = FungibleAsset::new(fee_parameters.native_asset_id(), 0)
98                .expect("native asset ID should be a valid fungible faucet ID");
99
100            // Map Asset to FungibleAsset.
101            // SAFETY: We requested a fungible vault key, so if Some is returned, it should be a
102            // fungible asset.
103            // A returned error means the vault does not track or does not contain the asset.
104            // However, since in practice, the partial vault represents the entire account vault,
105            // we can assume the second case. A returned None means the asset's amount is
106            // zero.
107            // So in both Err and None cases, use the default native_asset with amount 0.
108            account
109                .vault()
110                .get(native_asset.vault_key())
111                .map(|asset| asset.map(|asset| asset.unwrap_fungible()).unwrap_or(native_asset))
112                .unwrap_or(native_asset)
113        };
114
115        let base_host = TransactionBaseHost::new(
116            account,
117            input_notes,
118            mast_store,
119            scripts_mast_store,
120            acct_procedure_index_map,
121        );
122
123        Self {
124            base_host,
125            authenticator,
126            generated_signatures: BTreeMap::new(),
127            initial_native_asset,
128            source_manager,
129        }
130    }
131
132    // PUBLIC ACCESSORS
133    // --------------------------------------------------------------------------------------------
134
135    /// Returns a reference to the `tx_progress` field of this transaction host.
136    pub fn tx_progress(&self) -> &TransactionProgress {
137        self.base_host.tx_progress()
138    }
139
140    // EVENT HANDLERS
141    // --------------------------------------------------------------------------------------------
142
143    /// Pushes a signature to the advice stack as a response to the `AuthRequest` event.
144    ///
145    /// The signature is requested from the host's authenticator.
146    pub async fn on_auth_requested(
147        &mut self,
148        pub_key_hash: Word,
149        signing_inputs: SigningInputs,
150    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
151        let authenticator =
152            self.authenticator.ok_or(TransactionKernelError::MissingAuthenticator)?;
153
154        let signature: Vec<Felt> = authenticator
155            .get_signature(pub_key_hash, &signing_inputs)
156            .await
157            .map_err(|err| TransactionKernelError::SignatureGenerationFailed(Box::new(err)))?;
158
159        let signature_key = Hasher::merge(&[pub_key_hash, signing_inputs.to_commitment()]);
160
161        self.generated_signatures.insert(signature_key, signature.clone());
162
163        Ok(vec![AdviceMutation::extend_stack(signature)])
164    }
165
166    /// Handles the [`TransactionEvent::EpilogueTxFeeComputed`] and returns an error if the account
167    /// cannot pay the fee.
168    fn on_tx_fee_computed(
169        &self,
170        fee_asset: FungibleAsset,
171    ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
172        // Compute the current balance of the native asset in the account based on the initial value
173        // and the delta.
174        let current_native_asset = {
175            let native_asset_amount_delta = self
176                .base_host
177                .account_delta_tracker()
178                .vault_delta()
179                .fungible()
180                .amount(&self.initial_native_asset.faucet_id())
181                .unwrap_or(0);
182
183            // SAFETY: Initial native asset faucet ID should be a fungible faucet and amount should
184            // be less than MAX_AMOUNT as checked by the account delta.
185            let native_asset_delta = FungibleAsset::new(
186                self.initial_native_asset.faucet_id(),
187                native_asset_amount_delta.unsigned_abs(),
188            )
189            .expect("faucet ID and amount should be valid");
190
191            // SAFETY: These computations are essentially the same as the ones executed by the
192            // transaction kernel, which should have aborted if they weren't valid.
193            if native_asset_amount_delta > 0 {
194                self.initial_native_asset
195                    .add(native_asset_delta)
196                    .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT")
197            } else {
198                self.initial_native_asset
199                    .sub(native_asset_delta)
200                    .expect("transaction kernel should ensure amount is not negative")
201            }
202        };
203
204        // Return an error if the balance in the account does not cover the fee.
205        if current_native_asset.amount() < fee_asset.amount() {
206            return Err(TransactionKernelError::InsufficientFee {
207                account_balance: current_native_asset.amount(),
208                tx_fee: fee_asset.amount(),
209            });
210        }
211
212        Ok(Vec::new())
213    }
214
215    /// Consumes `self` and returns the account delta, output notes, generated signatures and
216    /// transaction progress.
217    pub fn into_parts(
218        self,
219    ) -> (AccountDelta, Vec<OutputNote>, BTreeMap<Word, Vec<Felt>>, TransactionProgress) {
220        let (account_delta, output_notes, tx_progress) = self.base_host.into_parts();
221
222        (account_delta, output_notes, self.generated_signatures, tx_progress)
223    }
224}
225
226// HOST IMPLEMENTATION
227// ================================================================================================
228
229impl<STORE, AUTH> BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
230where
231    STORE: MastForestStore,
232    AUTH: TransactionAuthenticator,
233{
234    fn get_mast_forest(&self, procedure_root: &Word) -> Option<Arc<MastForest>> {
235        self.base_host.get_mast_forest(procedure_root)
236    }
237
238    fn get_label_and_source_file(
239        &self,
240        location: &Location,
241    ) -> (SourceSpan, Option<Arc<SourceFile>>) {
242        let source_manager = self.source_manager.as_ref();
243        let maybe_file = source_manager.get_by_uri(location.uri());
244        let span = source_manager.location_to_span(location.clone()).unwrap_or_default();
245        (span, maybe_file)
246    }
247}
248
249impl<STORE, AUTH> AsyncHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
250where
251    STORE: MastForestStore + Sync,
252    AUTH: TransactionAuthenticator + Sync,
253{
254    fn on_event(
255        &mut self,
256        process: &ProcessState,
257        event_id: u32,
258    ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
259        // TODO: Eventually, refactor this to let TransactionEvent contain the data directly, which
260        // should be cleaner.
261        let event_handling_result = TransactionEvent::try_from(event_id)
262            .map_err(EventError::from)
263            .and_then(|transaction_event| self.base_host.handle_event(process, transaction_event));
264
265        async move {
266            let event_handling = event_handling_result?;
267            let event_data = match event_handling {
268                TransactionEventHandling::Unhandled(event) => event,
269                TransactionEventHandling::Handled(mutations) => {
270                    return Ok(mutations);
271                },
272            };
273
274            match event_data {
275                TransactionEventData::AuthRequest { pub_key_hash, signing_inputs } => self
276                    .on_auth_requested(pub_key_hash, signing_inputs)
277                    .await
278                    .map_err(EventError::from),
279                TransactionEventData::TransactionFeeComputed { fee_asset } => {
280                    self.on_tx_fee_computed(fee_asset).map_err(EventError::from)
281                },
282            }
283        }
284    }
285}