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
36pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
47where
48 STORE: MastForestStore,
49 AUTH: TransactionAuthenticator,
50{
51 base_host: TransactionBaseHost<'store, STORE>,
53
54 authenticator: Option<&'auth AUTH>,
57
58 generated_signatures: BTreeMap<Word, Vec<Felt>>,
64
65 initial_native_asset: FungibleAsset,
67
68 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 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 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 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 pub fn tx_progress(&self) -> &TransactionProgress {
137 self.base_host.tx_progress()
138 }
139
140 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 fn on_tx_fee_computed(
169 &self,
170 fee_asset: FungibleAsset,
171 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
172 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 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 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 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 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
226impl<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 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}