1use alloc::collections::BTreeMap;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use miden_lib::transaction::{EventId, TransactionAdviceInputs};
6use miden_objects::account::auth::PublicKeyCommitment;
7use miden_objects::account::{AccountCode, AccountDelta, AccountId, PartialAccount};
8use miden_objects::assembly::debuginfo::Location;
9use miden_objects::assembly::{SourceFile, SourceManagerSync, SourceSpan};
10use miden_objects::asset::{Asset, AssetVaultKey, AssetWitness, FungibleAsset};
11use miden_objects::block::BlockNumber;
12use miden_objects::crypto::merkle::SmtProof;
13use miden_objects::note::{NoteInputs, NoteMetadata, NoteRecipient};
14use miden_objects::transaction::{InputNote, InputNotes, OutputNote};
15use miden_objects::vm::AdviceMap;
16use miden_objects::{Felt, Hasher, Word};
17use miden_processor::{
18 AdviceMutation,
19 AsyncHost,
20 BaseHost,
21 EventError,
22 FutureMaybeSend,
23 MastForest,
24 ProcessState,
25};
26
27use crate::auth::{SigningInputs, TransactionAuthenticator};
28use crate::errors::TransactionKernelError;
29use crate::host::note_builder::OutputNoteBuilder;
30use crate::host::{
31 ScriptMastForestStore,
32 TransactionBaseHost,
33 TransactionEventData,
34 TransactionEventHandling,
35 TransactionProgress,
36};
37use crate::{AccountProcedureIndexMap, DataStore, DataStoreError};
38
39pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
50where
51 STORE: DataStore,
52 AUTH: TransactionAuthenticator,
53{
54 base_host: TransactionBaseHost<'store, STORE>,
56
57 authenticator: Option<&'auth AUTH>,
60
61 ref_block: BlockNumber,
63
64 accessed_foreign_account_code: Vec<AccountCode>,
68
69 generated_signatures: BTreeMap<Word, Vec<Felt>>,
75
76 source_manager: Arc<dyn SourceManagerSync>,
79}
80
81impl<'store, 'auth, STORE, AUTH> TransactionExecutorHost<'store, 'auth, STORE, AUTH>
82where
83 STORE: DataStore + Sync,
84 AUTH: TransactionAuthenticator + Sync,
85{
86 pub fn new(
91 account: &PartialAccount,
92 input_notes: InputNotes<InputNote>,
93 mast_store: &'store STORE,
94 scripts_mast_store: ScriptMastForestStore,
95 acct_procedure_index_map: AccountProcedureIndexMap,
96 authenticator: Option<&'auth AUTH>,
97 ref_block: BlockNumber,
98 source_manager: Arc<dyn SourceManagerSync>,
99 ) -> Self {
100 let base_host = TransactionBaseHost::new(
101 account,
102 input_notes,
103 mast_store,
104 scripts_mast_store,
105 acct_procedure_index_map,
106 );
107
108 Self {
109 base_host,
110 authenticator,
111 ref_block,
112 accessed_foreign_account_code: Vec::new(),
113 generated_signatures: BTreeMap::new(),
114 source_manager,
115 }
116 }
117
118 pub fn tx_progress(&self) -> &TransactionProgress {
123 self.base_host.tx_progress()
124 }
125
126 async fn on_foreign_account_requested(
131 &mut self,
132 foreign_account_id: AccountId,
133 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
134 let foreign_account_inputs = self
135 .base_host
136 .store()
137 .get_foreign_account_inputs(foreign_account_id, self.ref_block)
138 .await
139 .map_err(|err| TransactionKernelError::GetForeignAccountInputs {
140 foreign_account_id,
141 ref_block: self.ref_block,
142 source: err,
143 })?;
144
145 let mut tx_advice_inputs = TransactionAdviceInputs::default();
146 tx_advice_inputs
147 .add_foreign_accounts([&foreign_account_inputs])
148 .map_err(|err| {
149 TransactionKernelError::other_with_source(
150 format!(
151 "failed to construct advice inputs for foreign account {}",
152 foreign_account_inputs.id()
153 ),
154 err,
155 )
156 })?;
157
158 self.base_host
159 .load_foreign_account_code(foreign_account_inputs.code())
160 .map_err(|err| {
161 TransactionKernelError::other_with_source(
162 format!(
163 "failed to insert account procedures for foreign account {}",
164 foreign_account_inputs.id()
165 ),
166 err,
167 )
168 })?;
169
170 self.accessed_foreign_account_code.push(foreign_account_inputs.code().clone());
172
173 Ok(tx_advice_inputs.into_advice_mutations().collect())
174 }
175
176 pub async fn on_auth_requested(
180 &mut self,
181 pub_key_hash: Word,
182 signing_inputs: SigningInputs,
183 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
184 let authenticator =
185 self.authenticator.ok_or(TransactionKernelError::MissingAuthenticator)?;
186
187 let signature: Vec<Felt> = authenticator
188 .get_signature(PublicKeyCommitment::from(pub_key_hash), &signing_inputs)
189 .await
190 .map_err(TransactionKernelError::SignatureGenerationFailed)?
191 .to_prepared_signature();
192
193 let signature_key = Hasher::merge(&[pub_key_hash, signing_inputs.to_commitment()]);
194
195 self.generated_signatures.insert(signature_key, signature.clone());
196
197 Ok(vec![AdviceMutation::extend_stack(signature)])
198 }
199
200 async fn on_before_tx_fee_removed_from_account(
203 &self,
204 fee_asset: FungibleAsset,
205 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
206 let asset_witness = self
207 .base_host
208 .store()
209 .get_vault_asset_witness(
210 self.base_host.initial_account_header().id(),
211 self.base_host.initial_account_header().vault_root(),
212 fee_asset.vault_key(),
213 )
214 .await
215 .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
216 vault_root: self.base_host.initial_account_header().vault_root(),
217 asset_key: fee_asset.vault_key(),
218 source: err,
219 })?;
220
221 let initial_fee_asset = asset_witness
223 .find(fee_asset.vault_key())
224 .and_then(|asset| match asset {
225 Asset::Fungible(fungible_asset) => Some(fungible_asset),
226 _ => None,
227 })
228 .unwrap_or(
229 FungibleAsset::new(fee_asset.faucet_id(), 0)
230 .expect("fungible asset created from fee asset should be valid"),
231 );
232
233 let current_fee_asset = {
236 let fee_asset_amount_delta = self
237 .base_host
238 .account_delta_tracker()
239 .vault_delta()
240 .fungible()
241 .amount(&initial_fee_asset.faucet_id())
242 .unwrap_or(0);
243
244 let fee_asset_delta = FungibleAsset::new(
247 initial_fee_asset.faucet_id(),
248 fee_asset_amount_delta.unsigned_abs(),
249 )
250 .expect("faucet ID and amount should be valid");
251
252 if fee_asset_amount_delta > 0 {
255 initial_fee_asset
256 .add(fee_asset_delta)
257 .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT")
258 } else {
259 initial_fee_asset
260 .sub(fee_asset_delta)
261 .expect("transaction kernel should ensure amount is not negative")
262 }
263 };
264
265 if current_fee_asset.amount() < fee_asset.amount() {
267 return Err(TransactionKernelError::InsufficientFee {
268 account_balance: current_fee_asset.amount(),
269 tx_fee: fee_asset.amount(),
270 });
271 }
272
273 Ok(asset_witness_to_advice_mutation(asset_witness))
274 }
275
276 async fn on_account_storage_map_witness_requested(
281 &self,
282 current_account_id: AccountId,
283 map_root: Word,
284 map_key: Word,
285 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
286 let storage_map_witness = self
287 .base_host
288 .store()
289 .get_storage_map_witness(current_account_id, map_root, map_key)
290 .await
291 .map_err(|err| TransactionKernelError::GetStorageMapWitness {
292 map_root,
293 map_key,
294 source: err,
295 })?;
296
297 let merkle_store_ext =
299 AdviceMutation::extend_merkle_store(storage_map_witness.authenticated_nodes());
300
301 let smt_proof = SmtProof::from(storage_map_witness);
302 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
303 smt_proof.leaf().hash(),
304 smt_proof.leaf().to_elements(),
305 )]));
306
307 Ok(vec![merkle_store_ext, map_ext])
308 }
309
310 async fn on_account_vault_asset_witness_requested(
343 &self,
344 current_account_id: AccountId,
345 vault_root: Word,
346 asset_key: AssetVaultKey,
347 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
348 let asset_witness = self
349 .base_host
350 .store()
351 .get_vault_asset_witness(current_account_id, vault_root, asset_key)
352 .await
353 .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
354 vault_root,
355 asset_key,
356 source: err,
357 })?;
358
359 Ok(asset_witness_to_advice_mutation(asset_witness))
360 }
361
362 async fn on_note_script_requested(
368 &mut self,
369 script_root: Word,
370 metadata: NoteMetadata,
371 recipient_digest: Word,
372 note_idx: usize,
373 note_inputs: NoteInputs,
374 serial_num: Word,
375 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
376 let note_script_result = self.base_host.store().get_note_script(script_root).await;
377
378 let (recipient, mutations) = match note_script_result {
379 Ok(note_script) => {
380 let script_felts: Vec<Felt> = (¬e_script).into();
381 let recipient = NoteRecipient::new(serial_num, note_script, note_inputs);
382 let mutations = vec![AdviceMutation::extend_map(AdviceMap::from_iter([(
383 script_root,
384 script_felts,
385 )]))];
386
387 (Some(recipient), mutations)
388 },
389 Err(DataStoreError::NoteScriptNotFound(_)) if metadata.is_private() => {
390 (None, Vec::new())
391 },
392 Err(DataStoreError::NoteScriptNotFound(_)) => {
393 return Err(TransactionKernelError::other(format!(
394 "note script with root {script_root} not found in data store for public note"
395 )));
396 },
397 Err(err) => {
398 return Err(TransactionKernelError::other_with_source(
399 "failed to retrieve note script from data store",
400 err,
401 ));
402 },
403 };
404
405 let note_builder = OutputNoteBuilder::new(metadata, recipient_digest, recipient)?;
406 self.base_host.insert_output_note_builder(note_idx, note_builder)?;
407
408 Ok(mutations)
409 }
410
411 #[allow(clippy::type_complexity)]
414 pub fn into_parts(
415 self,
416 ) -> (
417 AccountDelta,
418 InputNotes<InputNote>,
419 Vec<OutputNote>,
420 Vec<AccountCode>,
421 BTreeMap<Word, Vec<Felt>>,
422 TransactionProgress,
423 ) {
424 let (account_delta, input_notes, output_notes, tx_progress) = self.base_host.into_parts();
425
426 (
427 account_delta,
428 input_notes,
429 output_notes,
430 self.accessed_foreign_account_code,
431 self.generated_signatures,
432 tx_progress,
433 )
434 }
435}
436
437impl<STORE, AUTH> BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
441where
442 STORE: DataStore,
443 AUTH: TransactionAuthenticator,
444{
445 fn get_label_and_source_file(
446 &self,
447 location: &Location,
448 ) -> (SourceSpan, Option<Arc<SourceFile>>) {
449 let source_manager = self.source_manager.as_ref();
450 let maybe_file = source_manager.get_by_uri(location.uri());
451 let span = source_manager.location_to_span(location.clone()).unwrap_or_default();
452 (span, maybe_file)
453 }
454}
455
456impl<STORE, AUTH> AsyncHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
457where
458 STORE: DataStore + Sync,
459 AUTH: TransactionAuthenticator + Sync,
460{
461 fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
462 let mast_forest = self.base_host.get_mast_forest(node_digest);
463 async move { mast_forest }
464 }
465
466 fn on_event(
467 &mut self,
468 process: &ProcessState,
469 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
470 let event_id = EventId::from_felt(process.get_stack_item(0));
471
472 let event_handling_result = self.base_host.handle_event(process, event_id);
475
476 async move {
477 let event_handling = event_handling_result?;
478 let event_data = match event_handling {
479 TransactionEventHandling::Unhandled(event) => event,
480 TransactionEventHandling::Handled(mutations) => {
481 return Ok(mutations);
482 },
483 };
484
485 match event_data {
486 TransactionEventData::AuthRequest { pub_key_hash, signing_inputs } => self
487 .on_auth_requested(pub_key_hash, signing_inputs)
488 .await
489 .map_err(EventError::from),
490 TransactionEventData::TransactionFeeComputed { fee_asset } => self
491 .on_before_tx_fee_removed_from_account(fee_asset)
492 .await
493 .map_err(EventError::from),
494 TransactionEventData::ForeignAccount { account_id } => {
495 self.on_foreign_account_requested(account_id).await.map_err(EventError::from)
496 },
497 TransactionEventData::AccountVaultAssetWitness {
498 current_account_id,
499 vault_root,
500 asset_key,
501 } => self
502 .on_account_vault_asset_witness_requested(
503 current_account_id,
504 vault_root,
505 asset_key,
506 )
507 .await
508 .map_err(EventError::from),
509 TransactionEventData::AccountStorageMapWitness {
510 current_account_id,
511 map_root,
512 map_key,
513 } => self
514 .on_account_storage_map_witness_requested(current_account_id, map_root, map_key)
515 .await
516 .map_err(EventError::from),
517 TransactionEventData::NoteData {
518 note_idx,
519 metadata,
520 script_root,
521 recipient_digest,
522 note_inputs,
523 serial_num,
524 } => self
525 .on_note_script_requested(
526 script_root,
527 metadata,
528 recipient_digest,
529 note_idx,
530 note_inputs,
531 serial_num,
532 )
533 .await
534 .map_err(EventError::from),
535 }
536 }
537 }
538}
539
540fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> Vec<AdviceMutation> {
546 let merkle_store_ext = AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes());
548
549 let smt_proof = SmtProof::from(asset_witness);
550 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
551 smt_proof.leaf().hash(),
552 smt_proof.leaf().to_elements(),
553 )]));
554
555 vec![merkle_store_ext, map_ext]
556}