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::{BaseHost, 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::{
26 NoteRecipient,
27 NoteScript,
28 NoteScriptRoot,
29 NoteStorage,
30 PartialNoteMetadata,
31};
32use miden_protocol::transaction::{
33 InputNote,
34 InputNotes,
35 RawOutputNote,
36 TransactionAdviceInputs,
37 TransactionSummary,
38};
39use miden_protocol::vm::{AdviceMap, EventId, EventName};
40use miden_protocol::{Felt, Hasher, Word};
41use miden_standards::note::StandardNote;
42
43use crate::auth::{SigningInputs, TransactionAuthenticator};
44use crate::errors::TransactionKernelError;
45use crate::host::{
46 RecipientData,
47 ScriptMastForestStore,
48 TransactionBaseHost,
49 TransactionEvent,
50 TransactionProgress,
51 TransactionProgressEvent,
52};
53use crate::{AccountProcedureIndexMap, DataStore};
54
55pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
66where
67 STORE: DataStore,
68 AUTH: TransactionAuthenticator,
69{
70 base_host: TransactionBaseHost<'store, STORE>,
72
73 tx_progress: TransactionProgress,
77
78 authenticator: Option<&'auth AUTH>,
81
82 ref_block: BlockNumber,
84
85 accessed_foreign_account_code: Vec<AccountCode>,
89
90 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
92
93 generated_signatures: BTreeMap<Word, Vec<Felt>>,
99
100 initial_fee_asset_balance: u64,
102
103 source_manager: Arc<dyn SourceManagerSync>,
106}
107
108impl<'store, 'auth, STORE, AUTH> TransactionExecutorHost<'store, 'auth, STORE, AUTH>
109where
110 STORE: DataStore + Sync,
111 AUTH: TransactionAuthenticator + Sync,
112{
113 #[allow(clippy::too_many_arguments)]
118 pub fn new(
119 account: &PartialAccount,
120 input_notes: InputNotes<InputNote>,
121 mast_store: &'store STORE,
122 scripts_mast_store: ScriptMastForestStore,
123 acct_procedure_index_map: AccountProcedureIndexMap,
124 authenticator: Option<&'auth AUTH>,
125 ref_block: BlockNumber,
126 initial_fee_asset_balance: u64,
127 source_manager: Arc<dyn SourceManagerSync>,
128 ) -> Self {
129 let base_host = TransactionBaseHost::new(
130 account,
131 input_notes,
132 mast_store,
133 scripts_mast_store,
134 acct_procedure_index_map,
135 );
136
137 Self {
138 base_host,
139 tx_progress: TransactionProgress::default(),
140 authenticator,
141 ref_block,
142 accessed_foreign_account_code: Vec::new(),
143 foreign_account_slot_names: BTreeMap::new(),
144 generated_signatures: BTreeMap::new(),
145 initial_fee_asset_balance,
146 source_manager,
147 }
148 }
149
150 pub fn tx_progress(&self) -> &TransactionProgress {
155 &self.tx_progress
156 }
157
158 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
160 &self.foreign_account_slot_names
161 }
162
163 async fn on_foreign_account_requested(
168 &mut self,
169 foreign_account_id: AccountId,
170 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
171 let foreign_account_inputs = self
172 .base_host
173 .store()
174 .get_foreign_account_inputs(foreign_account_id, self.ref_block)
175 .await
176 .map_err(|err| TransactionKernelError::GetForeignAccountInputs {
177 foreign_account_id,
178 ref_block: self.ref_block,
179 source: err,
180 })?;
181
182 let mut tx_advice_inputs = TransactionAdviceInputs::default();
183 tx_advice_inputs.add_foreign_accounts([&foreign_account_inputs]);
184
185 foreign_account_inputs.storage().header().slots().for_each(|slot| {
187 self.foreign_account_slot_names.insert(slot.id(), slot.name().clone());
188 });
189
190 self.base_host.load_foreign_account_code(foreign_account_inputs.code());
191
192 self.accessed_foreign_account_code.push(foreign_account_inputs.code().clone());
194
195 Ok(tx_advice_inputs.into_advice_mutations().collect())
196 }
197
198 pub async fn on_auth_requested(
202 &mut self,
203 pub_key_hash: Word,
204 tx_summary: TransactionSummary,
205 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
206 let signing_inputs = SigningInputs::TransactionSummary(Box::new(tx_summary));
207
208 let authenticator =
209 self.authenticator.ok_or(TransactionKernelError::MissingAuthenticator)?;
210
211 let message = signing_inputs.to_commitment();
213
214 let signature: Vec<Felt> = authenticator
215 .get_signature(PublicKeyCommitment::from(pub_key_hash), &signing_inputs)
216 .await
217 .map_err(TransactionKernelError::SignatureGenerationFailed)?
218 .to_prepared_signature(message);
219
220 let signature_key = Hasher::merge(&[pub_key_hash, message]);
221 self.generated_signatures.insert(signature_key, signature.clone());
222
223 Ok(vec![AdviceMutation::extend_stack(signature)])
224 }
225
226 async fn on_before_tx_fee_removed_from_account(
229 &self,
230 fee_asset: FungibleAsset,
231 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
232 let initial_fee_asset =
234 FungibleAsset::new(fee_asset.faucet_id(), self.initial_fee_asset_balance)
235 .expect("fungible asset created from fee asset should be valid");
236
237 let current_fee_asset = {
240 let fee_asset_amount_delta = self
241 .base_host
242 .account_delta_tracker()
243 .vault_delta()
244 .fungible()
245 .amount(&initial_fee_asset.vault_key())
246 .unwrap_or(0);
247
248 let fee_asset_delta = FungibleAsset::new(
251 initial_fee_asset.faucet_id(),
252 fee_asset_amount_delta.unsigned_abs(),
253 )
254 .expect("faucet ID and amount should be valid");
255
256 if fee_asset_amount_delta > 0 {
259 initial_fee_asset
260 .add(fee_asset_delta)
261 .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT")
262 } else {
263 initial_fee_asset
264 .sub(fee_asset_delta)
265 .expect("transaction kernel should ensure amount is not negative")
266 }
267 };
268
269 if current_fee_asset.amount() < fee_asset.amount() {
271 return Err(TransactionKernelError::InsufficientFee {
272 account_balance: current_fee_asset.amount().as_u64(),
273 tx_fee: fee_asset.amount().as_u64(),
274 });
275 }
276
277 Ok(Vec::new())
278 }
279
280 async fn on_account_storage_map_witness_requested(
285 &self,
286 active_account_id: AccountId,
287 map_root: Word,
288 map_key: StorageMapKey,
289 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
290 let storage_map_witness = self
291 .base_host
292 .store()
293 .get_storage_map_witness(active_account_id, map_root, map_key)
294 .await
295 .map_err(|err| TransactionKernelError::GetStorageMapWitness {
296 map_root,
297 map_key,
298 source: err,
299 })?;
300
301 let merkle_store_ext =
303 AdviceMutation::extend_merkle_store(storage_map_witness.authenticated_nodes());
304
305 let smt_proof = SmtProof::from(storage_map_witness);
306 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
307 smt_proof.leaf().hash(),
308 smt_proof.leaf().to_elements().collect::<Vec<_>>(),
309 )]));
310
311 Ok(vec![merkle_store_ext, map_ext])
312 }
313
314 async fn on_account_vault_asset_witness_requested(
347 &self,
348 active_account_id: AccountId,
349 vault_root: Word,
350 asset_key: AssetVaultKey,
351 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
352 let asset_witnesses = self
353 .base_host
354 .store()
355 .get_vault_asset_witnesses(
356 active_account_id,
357 vault_root,
358 BTreeSet::from_iter([asset_key]),
359 )
360 .await
361 .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
362 vault_root,
363 asset_key,
364 source: err,
365 })?;
366
367 Ok(asset_witnesses.into_iter().flat_map(asset_witness_to_advice_mutation).collect())
368 }
369
370 async fn on_note_script_requested(
387 &mut self,
388 note_idx: usize,
389 recipient_digest: Word,
390 script_root: Word,
391 metadata: PartialNoteMetadata,
392 note_storage: NoteStorage,
393 serial_num: Word,
394 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
395 let script_root = NoteScriptRoot::from_raw(script_root);
397 let note_script: Option<NoteScript> =
398 if let Some(standard_note) = StandardNote::from_script_root(script_root) {
399 Some(standard_note.script())
400 } else {
401 self.base_host.store().get_note_script(script_root).await.map_err(|err| {
402 TransactionKernelError::other_with_source(
403 "failed to retrieve note script from data store",
404 err,
405 )
406 })?
407 };
408
409 match note_script {
410 Some(note_script) => {
411 let script_felts: Vec<Felt> = (¬e_script).into();
412 let recipient = NoteRecipient::new(serial_num, note_script, note_storage);
413
414 if recipient.digest() != recipient_digest {
415 return Err(TransactionKernelError::other(format!(
416 "recipient digest is {recipient_digest}, but recipient constructed from raw inputs has digest {}",
417 recipient.digest()
418 )));
419 }
420
421 self.base_host.output_note_from_recipient(note_idx, metadata, recipient)?;
422
423 Ok(vec![AdviceMutation::extend_map(AdviceMap::from_iter([(
424 Word::from(script_root),
425 script_felts,
426 )]))])
427 },
428 None if metadata.is_private() => {
429 self.base_host.output_note_from_recipient_digest(
430 note_idx,
431 metadata,
432 recipient_digest,
433 )?;
434
435 Ok(Vec::new())
436 },
437 None => Err(TransactionKernelError::other(format!(
438 "note script with root {} not found in data store for public note",
439 Word::from(script_root),
440 ))),
441 }
442 }
443
444 #[allow(clippy::type_complexity)]
447 pub fn into_parts(
448 self,
449 ) -> (
450 AccountDelta,
451 InputNotes<InputNote>,
452 Vec<RawOutputNote>,
453 Vec<AccountCode>,
454 BTreeMap<Word, Vec<Felt>>,
455 TransactionProgress,
456 BTreeMap<StorageSlotId, StorageSlotName>,
457 ) {
458 let (account_delta, input_notes, output_notes) = self.base_host.into_parts();
459
460 (
461 account_delta,
462 input_notes,
463 output_notes,
464 self.accessed_foreign_account_code,
465 self.generated_signatures,
466 self.tx_progress,
467 self.foreign_account_slot_names,
468 )
469 }
470}
471
472impl<STORE, AUTH> BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
476where
477 STORE: DataStore + Sync,
478 AUTH: TransactionAuthenticator + Sync,
479{
480 fn get_label_and_source_file(
481 &self,
482 location: &Location,
483 ) -> (SourceSpan, Option<Arc<SourceFile>>) {
484 let source_manager = self.source_manager.as_ref();
485 let maybe_file = source_manager.get_by_uri(location.uri());
486 let span = source_manager.location_to_span(location.clone()).unwrap_or_default();
487 (span, maybe_file)
488 }
489
490 fn resolve_event(&self, event_id: EventId) -> Option<&EventName> {
491 self.base_host.resolve_event(event_id)
492 }
493}
494
495impl<STORE, AUTH> Host for TransactionExecutorHost<'_, '_, STORE, AUTH>
496where
497 STORE: DataStore + Sync,
498 AUTH: TransactionAuthenticator + Sync,
499{
500 fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
501 let mast_forest = self.base_host.get_mast_forest(node_digest);
502 async move { mast_forest }
503 }
504
505 fn on_event(
506 &mut self,
507 process: &ProcessorState,
508 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
509 let core_lib_event_result = self.base_host.handle_core_lib_events(process);
510
511 let tx_event_result = match core_lib_event_result {
515 Ok(None) => Some(TransactionEvent::extract(&self.base_host, process)),
516 _ => None,
517 };
518
519 async move {
520 if let Some(mutations) = core_lib_event_result? {
521 return Ok(mutations);
522 }
523
524 let Some(tx_event_result) = tx_event_result else {
526 return Ok(Vec::new());
527 };
528 let Some(tx_event) = tx_event_result? else {
530 return Ok(Vec::new());
531 };
532
533 let result = match tx_event {
534 TransactionEvent::AccountBeforeForeignLoad { foreign_account_id: account_id } => {
535 self.on_foreign_account_requested(account_id).await
536 },
537
538 TransactionEvent::AccountVaultAfterRemoveAsset { asset } => {
539 self.base_host.on_account_vault_after_remove_asset(asset)
540 },
541 TransactionEvent::AccountVaultAfterAddAsset { asset } => {
542 self.base_host.on_account_vault_after_add_asset(asset)
543 },
544
545 TransactionEvent::AccountStorageAfterSetItem { slot_name, new_value } => {
546 self.base_host.on_account_storage_after_set_item(slot_name, new_value)
547 },
548
549 TransactionEvent::AccountStorageAfterSetMapItem {
550 slot_name,
551 key,
552 old_value: prev_map_value,
553 new_value,
554 } => self.base_host.on_account_storage_after_set_map_item(
555 slot_name,
556 key,
557 prev_map_value,
558 new_value,
559 ),
560
561 TransactionEvent::AccountVaultBeforeAssetAccess {
562 active_account_id,
563 vault_root,
564 asset_key,
565 } => {
566 self.on_account_vault_asset_witness_requested(
567 active_account_id,
568 vault_root,
569 asset_key,
570 )
571 .await
572 },
573
574 TransactionEvent::AccountStorageBeforeMapItemAccess {
575 active_account_id,
576 map_root,
577 map_key,
578 } => {
579 self.on_account_storage_map_witness_requested(
580 active_account_id,
581 map_root,
582 map_key,
583 )
584 .await
585 },
586
587 TransactionEvent::AccountAfterIncrementNonce => {
588 self.base_host.on_account_after_increment_nonce()
589 },
590
591 TransactionEvent::AccountPushProcedureIndex { code_commitment, procedure_root } => {
592 self.base_host.on_account_push_procedure_index(code_commitment, procedure_root)
593 },
594
595 TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => {
596 match recipient_data {
597 RecipientData::Digest(recipient_digest) => {
598 self.base_host.output_note_from_recipient_digest(
599 note_idx,
600 metadata,
601 recipient_digest,
602 )
603 },
604 RecipientData::Recipient(note_recipient) => self
605 .base_host
606 .output_note_from_recipient(note_idx, metadata, note_recipient),
607 RecipientData::ScriptMissing {
608 recipient_digest,
609 serial_num,
610 script_root,
611 note_storage,
612 } => {
613 self.on_note_script_requested(
614 note_idx,
615 recipient_digest,
616 script_root,
617 metadata,
618 note_storage,
619 serial_num,
620 )
621 .await
622 },
623 }
624 },
625
626 TransactionEvent::NoteBeforeAddAsset { note_idx, asset } => {
627 self.base_host.on_note_before_add_asset(note_idx, asset)
628 },
629
630 TransactionEvent::NoteBeforeAddAttachment { note_idx, attachment } => self
631 .base_host
632 .on_note_before_add_attachment(note_idx, attachment)
633 .map(|_| Vec::new()),
634
635 TransactionEvent::AuthRequest { pub_key_hash, tx_summary, signature } => {
636 if let Some(signature) = signature {
637 Ok(self.base_host.on_auth_requested(signature))
638 } else {
639 self.on_auth_requested(pub_key_hash, tx_summary).await
640 }
641 },
642
643 TransactionEvent::Unauthorized { tx_summary } => {
645 Err(TransactionKernelError::Unauthorized(Box::new(tx_summary)))
646 },
647
648 TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount { fee_asset } => {
649 self.on_before_tx_fee_removed_from_account(fee_asset).await
650 },
651
652 TransactionEvent::LinkMapSet { advice_mutation } => Ok(advice_mutation),
653 TransactionEvent::LinkMapGet { advice_mutation } => Ok(advice_mutation),
654 TransactionEvent::Progress(tx_progress) => match tx_progress {
655 TransactionProgressEvent::PrologueStart(clk) => {
656 self.tx_progress.start_prologue(clk);
657 Ok(Vec::new())
658 },
659 TransactionProgressEvent::PrologueEnd(clk) => {
660 self.tx_progress.end_prologue(clk);
661 Ok(Vec::new())
662 },
663 TransactionProgressEvent::NotesProcessingStart(clk) => {
664 self.tx_progress.start_notes_processing(clk);
665 Ok(Vec::new())
666 },
667 TransactionProgressEvent::NotesProcessingEnd(clk) => {
668 self.tx_progress.end_notes_processing(clk);
669 Ok(Vec::new())
670 },
671 TransactionProgressEvent::NoteExecutionStart { note_id, clk } => {
672 self.tx_progress.start_note_execution(clk, note_id);
673 Ok(Vec::new())
674 },
675 TransactionProgressEvent::NoteExecutionEnd(clk) => {
676 self.tx_progress.end_note_execution(clk);
677 Ok(Vec::new())
678 },
679 TransactionProgressEvent::TxScriptProcessingStart(clk) => {
680 self.tx_progress.start_tx_script_processing(clk);
681 Ok(Vec::new())
682 },
683 TransactionProgressEvent::TxScriptProcessingEnd(clk) => {
684 self.tx_progress.end_tx_script_processing(clk);
685 Ok(Vec::new())
686 },
687 TransactionProgressEvent::EpilogueStart(clk) => {
688 self.tx_progress.start_epilogue(clk);
689 Ok(Vec::new())
690 },
691 TransactionProgressEvent::EpilogueEnd(clk) => {
692 self.tx_progress.end_epilogue(clk);
693 Ok(Vec::new())
694 },
695 TransactionProgressEvent::EpilogueAuthProcStart(clk) => {
696 self.tx_progress.start_auth_procedure(clk);
697 Ok(Vec::new())
698 },
699 TransactionProgressEvent::EpilogueAuthProcEnd(clk) => {
700 self.tx_progress.end_auth_procedure(clk);
701 Ok(Vec::new())
702 },
703 TransactionProgressEvent::EpilogueAfterTxCyclesObtained(clk) => {
704 self.tx_progress.epilogue_after_tx_cycles_obtained(clk);
705 Ok(Vec::new())
706 },
707 },
708 };
709
710 result.map_err(EventError::from)
711 }
712 }
713}
714
715fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> [AdviceMutation; 2] {
721 let merkle_store_ext = AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes());
723
724 let smt_proof = SmtProof::from(asset_witness);
725 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
726 smt_proof.leaf().hash(),
727 smt_proof.leaf().to_elements().collect::<Vec<_>>(),
728 )]));
729
730 [merkle_store_ext, map_ext]
731}