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
49pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
60where
61 STORE: DataStore,
62 AUTH: TransactionAuthenticator,
63{
64 base_host: TransactionBaseHost<'store, STORE>,
66
67 tx_progress: TransactionProgress,
71
72 authenticator: Option<&'auth AUTH>,
75
76 ref_block: BlockNumber,
78
79 accessed_foreign_account_code: Vec<AccountCode>,
83
84 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
86
87 generated_signatures: BTreeMap<Word, Vec<Felt>>,
93
94 initial_fee_asset_balance: u64,
96
97 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 #[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 pub fn tx_progress(&self) -> &TransactionProgress {
149 &self.tx_progress
150 }
151
152 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
154 &self.foreign_account_slot_names
155 }
156
157 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 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 self.accessed_foreign_account_code.push(foreign_account_inputs.code().clone());
188
189 Ok(tx_advice_inputs.into_advice_mutations().collect())
190 }
191
192 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 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 async fn on_before_tx_fee_removed_from_account(
223 &self,
224 fee_asset: FungibleAsset,
225 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
226 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 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 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 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 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 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 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 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 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 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> = (¬e_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 #[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
464impl<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 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 let Some(tx_event_result) = tx_event_result else {
508 return Ok(Vec::new());
509 };
510 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 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
701fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> [AdviceMutation; 2] {
707 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}