1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_processor::{
7 AdviceMutation,
8 AsyncHost,
9 BaseHost,
10 EventError,
11 FutureMaybeSend,
12 MastForest,
13 ProcessState,
14};
15use miden_protocol::account::auth::PublicKeyCommitment;
16use miden_protocol::account::{
17 AccountCode,
18 AccountDelta,
19 AccountId,
20 PartialAccount,
21 StorageSlotId,
22 StorageSlotName,
23};
24use miden_protocol::assembly::debuginfo::Location;
25use miden_protocol::assembly::{SourceFile, SourceManagerSync, SourceSpan};
26use miden_protocol::asset::{AssetVaultKey, AssetWitness, FungibleAsset};
27use miden_protocol::block::BlockNumber;
28use miden_protocol::crypto::merkle::smt::SmtProof;
29use miden_protocol::note::{NoteInputs, NoteMetadata, NoteRecipient};
30use miden_protocol::transaction::{
31 InputNote,
32 InputNotes,
33 OutputNote,
34 TransactionAdviceInputs,
35 TransactionSummary,
36};
37use miden_protocol::vm::AdviceMap;
38use miden_protocol::{Felt, Hasher, Word};
39
40use crate::auth::{SigningInputs, TransactionAuthenticator};
41use crate::errors::TransactionKernelError;
42use crate::host::{
43 RecipientData,
44 ScriptMastForestStore,
45 TransactionBaseHost,
46 TransactionEvent,
47 TransactionProgress,
48 TransactionProgressEvent,
49};
50use crate::{AccountProcedureIndexMap, DataStore};
51
52pub struct TransactionExecutorHost<'store, 'auth, STORE, AUTH>
63where
64 STORE: DataStore,
65 AUTH: TransactionAuthenticator,
66{
67 base_host: TransactionBaseHost<'store, STORE>,
69
70 tx_progress: TransactionProgress,
74
75 authenticator: Option<&'auth AUTH>,
78
79 ref_block: BlockNumber,
81
82 accessed_foreign_account_code: Vec<AccountCode>,
86
87 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
89
90 generated_signatures: BTreeMap<Word, Vec<Felt>>,
96
97 initial_fee_asset_balance: u64,
99
100 source_manager: Arc<dyn SourceManagerSync>,
103}
104
105impl<'store, 'auth, STORE, AUTH> TransactionExecutorHost<'store, 'auth, STORE, AUTH>
106where
107 STORE: DataStore + Sync,
108 AUTH: TransactionAuthenticator + Sync,
109{
110 #[allow(clippy::too_many_arguments)]
115 pub fn new(
116 account: &PartialAccount,
117 input_notes: InputNotes<InputNote>,
118 mast_store: &'store STORE,
119 scripts_mast_store: ScriptMastForestStore,
120 acct_procedure_index_map: AccountProcedureIndexMap,
121 authenticator: Option<&'auth AUTH>,
122 ref_block: BlockNumber,
123 initial_fee_asset_balance: u64,
124 source_manager: Arc<dyn SourceManagerSync>,
125 ) -> Self {
126 let base_host = TransactionBaseHost::new(
127 account,
128 input_notes,
129 mast_store,
130 scripts_mast_store,
131 acct_procedure_index_map,
132 );
133
134 Self {
135 base_host,
136 tx_progress: TransactionProgress::default(),
137 authenticator,
138 ref_block,
139 accessed_foreign_account_code: Vec::new(),
140 foreign_account_slot_names: BTreeMap::new(),
141 generated_signatures: BTreeMap::new(),
142 initial_fee_asset_balance,
143 source_manager,
144 }
145 }
146
147 pub fn tx_progress(&self) -> &TransactionProgress {
152 &self.tx_progress
153 }
154
155 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
157 &self.foreign_account_slot_names
158 }
159
160 async fn on_foreign_account_requested(
165 &mut self,
166 foreign_account_id: AccountId,
167 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
168 let foreign_account_inputs = self
169 .base_host
170 .store()
171 .get_foreign_account_inputs(foreign_account_id, self.ref_block)
172 .await
173 .map_err(|err| TransactionKernelError::GetForeignAccountInputs {
174 foreign_account_id,
175 ref_block: self.ref_block,
176 source: err,
177 })?;
178
179 let mut tx_advice_inputs = TransactionAdviceInputs::default();
180 tx_advice_inputs.add_foreign_accounts([&foreign_account_inputs]);
181
182 foreign_account_inputs.storage().header().slots().for_each(|slot| {
184 self.foreign_account_slot_names.insert(slot.id(), slot.name().clone());
185 });
186
187 self.base_host.load_foreign_account_code(foreign_account_inputs.code());
188
189 self.accessed_foreign_account_code.push(foreign_account_inputs.code().clone());
191
192 Ok(tx_advice_inputs.into_advice_mutations().collect())
193 }
194
195 pub async fn on_auth_requested(
199 &mut self,
200 pub_key_hash: Word,
201 tx_summary: TransactionSummary,
202 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
203 let signing_inputs = SigningInputs::TransactionSummary(Box::new(tx_summary));
204
205 let authenticator =
206 self.authenticator.ok_or(TransactionKernelError::MissingAuthenticator)?;
207
208 let message = signing_inputs.to_commitment();
210
211 let signature: Vec<Felt> = authenticator
212 .get_signature(PublicKeyCommitment::from(pub_key_hash), &signing_inputs)
213 .await
214 .map_err(TransactionKernelError::SignatureGenerationFailed)?
215 .to_prepared_signature(message);
216
217 let signature_key = Hasher::merge(&[pub_key_hash, message]);
218 self.generated_signatures.insert(signature_key, signature.clone());
219
220 Ok(vec![AdviceMutation::extend_stack(signature)])
221 }
222
223 async fn on_before_tx_fee_removed_from_account(
226 &self,
227 fee_asset: FungibleAsset,
228 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
229 let initial_fee_asset =
231 FungibleAsset::new(fee_asset.faucet_id(), self.initial_fee_asset_balance)
232 .expect("fungible asset created from fee asset should be valid");
233
234 let current_fee_asset = {
237 let fee_asset_amount_delta = self
238 .base_host
239 .account_delta_tracker()
240 .vault_delta()
241 .fungible()
242 .amount(&initial_fee_asset.faucet_id())
243 .unwrap_or(0);
244
245 let fee_asset_delta = FungibleAsset::new(
248 initial_fee_asset.faucet_id(),
249 fee_asset_amount_delta.unsigned_abs(),
250 )
251 .expect("faucet ID and amount should be valid");
252
253 if fee_asset_amount_delta > 0 {
256 initial_fee_asset
257 .add(fee_asset_delta)
258 .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT")
259 } else {
260 initial_fee_asset
261 .sub(fee_asset_delta)
262 .expect("transaction kernel should ensure amount is not negative")
263 }
264 };
265
266 if current_fee_asset.amount() < fee_asset.amount() {
268 return Err(TransactionKernelError::InsufficientFee {
269 account_balance: current_fee_asset.amount(),
270 tx_fee: fee_asset.amount(),
271 });
272 }
273
274 Ok(Vec::new())
275 }
276
277 async fn on_account_storage_map_witness_requested(
282 &self,
283 active_account_id: AccountId,
284 map_root: Word,
285 map_key: Word,
286 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
287 let storage_map_witness = self
288 .base_host
289 .store()
290 .get_storage_map_witness(active_account_id, map_root, map_key)
291 .await
292 .map_err(|err| TransactionKernelError::GetStorageMapWitness {
293 map_root,
294 map_key,
295 source: err,
296 })?;
297
298 let merkle_store_ext =
300 AdviceMutation::extend_merkle_store(storage_map_witness.authenticated_nodes());
301
302 let smt_proof = SmtProof::from(storage_map_witness);
303 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
304 smt_proof.leaf().hash(),
305 smt_proof.leaf().to_elements(),
306 )]));
307
308 Ok(vec![merkle_store_ext, map_ext])
309 }
310
311 async fn on_account_vault_asset_witness_requested(
344 &self,
345 active_account_id: AccountId,
346 vault_root: Word,
347 asset_key: AssetVaultKey,
348 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
349 let asset_witnesses = self
350 .base_host
351 .store()
352 .get_vault_asset_witnesses(
353 active_account_id,
354 vault_root,
355 BTreeSet::from_iter([asset_key]),
356 )
357 .await
358 .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
359 vault_root,
360 asset_key,
361 source: err,
362 })?;
363
364 Ok(asset_witnesses.into_iter().flat_map(asset_witness_to_advice_mutation).collect())
365 }
366
367 async fn on_note_script_requested(
373 &mut self,
374 note_idx: usize,
375 recipient_digest: Word,
376 script_root: Word,
377 metadata: NoteMetadata,
378 note_inputs: NoteInputs,
379 serial_num: Word,
380 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
381 let note_script_result = self.base_host.store().get_note_script(script_root).await;
382
383 match note_script_result {
384 Ok(Some(note_script)) => {
385 let script_felts: Vec<Felt> = (¬e_script).into();
386 let recipient = NoteRecipient::new(serial_num, note_script, note_inputs);
387
388 if recipient.digest() != recipient_digest {
389 return Err(TransactionKernelError::other(format!(
390 "recipient digest is {recipient_digest}, but recipient constructed from raw inputs has digest {}",
391 recipient.digest()
392 )));
393 }
394
395 self.base_host.output_note_from_recipient(note_idx, metadata, recipient)?;
396
397 Ok(vec![AdviceMutation::extend_map(AdviceMap::from_iter([(
398 script_root,
399 script_felts,
400 )]))])
401 },
402 Ok(None) if metadata.is_private() => {
403 self.base_host.output_note_from_recipient_digest(
404 note_idx,
405 metadata,
406 recipient_digest,
407 )?;
408
409 Ok(Vec::new())
410 },
411 Ok(None) => Err(TransactionKernelError::other(format!(
412 "note script with root {script_root} not found in data store for public note"
413 ))),
414 Err(err) => Err(TransactionKernelError::other_with_source(
415 "failed to retrieve note script from data store",
416 err,
417 )),
418 }
419 }
420
421 #[allow(clippy::type_complexity)]
424 pub fn into_parts(
425 self,
426 ) -> (
427 AccountDelta,
428 InputNotes<InputNote>,
429 Vec<OutputNote>,
430 Vec<AccountCode>,
431 BTreeMap<Word, Vec<Felt>>,
432 TransactionProgress,
433 BTreeMap<StorageSlotId, StorageSlotName>,
434 ) {
435 let (account_delta, input_notes, output_notes) = self.base_host.into_parts();
436
437 (
438 account_delta,
439 input_notes,
440 output_notes,
441 self.accessed_foreign_account_code,
442 self.generated_signatures,
443 self.tx_progress,
444 self.foreign_account_slot_names,
445 )
446 }
447}
448
449impl<STORE, AUTH> BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
453where
454 STORE: DataStore,
455 AUTH: TransactionAuthenticator,
456{
457 fn get_label_and_source_file(
458 &self,
459 location: &Location,
460 ) -> (SourceSpan, Option<Arc<SourceFile>>) {
461 let source_manager = self.source_manager.as_ref();
462 let maybe_file = source_manager.get_by_uri(location.uri());
463 let span = source_manager.location_to_span(location.clone()).unwrap_or_default();
464 (span, maybe_file)
465 }
466}
467
468impl<STORE, AUTH> AsyncHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
469where
470 STORE: DataStore + Sync,
471 AUTH: TransactionAuthenticator + Sync,
472{
473 fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
474 let mast_forest = self.base_host.get_mast_forest(node_digest);
475 async move { mast_forest }
476 }
477
478 fn on_event(
479 &mut self,
480 process: &ProcessState,
481 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
482 let core_lib_event_result = self.base_host.handle_core_lib_events(process);
483
484 let tx_event_result = match core_lib_event_result {
488 Ok(None) => Some(TransactionEvent::extract(&self.base_host, process)),
489 _ => None,
490 };
491
492 async move {
493 if let Some(mutations) = core_lib_event_result? {
494 return Ok(mutations);
495 }
496
497 let Some(tx_event_result) = tx_event_result else {
499 return Ok(Vec::new());
500 };
501 let Some(tx_event) = tx_event_result? else {
503 return Ok(Vec::new());
504 };
505
506 let result = match tx_event {
507 TransactionEvent::AccountBeforeForeignLoad { foreign_account_id: account_id } => {
508 self.on_foreign_account_requested(account_id).await
509 },
510
511 TransactionEvent::AccountVaultAfterRemoveAsset { asset } => {
512 self.base_host.on_account_vault_after_remove_asset(asset)
513 },
514 TransactionEvent::AccountVaultAfterAddAsset { asset } => {
515 self.base_host.on_account_vault_after_add_asset(asset)
516 },
517
518 TransactionEvent::AccountStorageAfterSetItem { slot_name, new_value } => {
519 self.base_host.on_account_storage_after_set_item(slot_name, new_value)
520 },
521
522 TransactionEvent::AccountStorageAfterSetMapItem {
523 slot_name,
524 key,
525 old_value: prev_map_value,
526 new_value,
527 } => self.base_host.on_account_storage_after_set_map_item(
528 slot_name,
529 key,
530 prev_map_value,
531 new_value,
532 ),
533
534 TransactionEvent::AccountVaultBeforeAssetAccess {
535 active_account_id,
536 vault_root,
537 asset_key,
538 } => {
539 self.on_account_vault_asset_witness_requested(
540 active_account_id,
541 vault_root,
542 asset_key,
543 )
544 .await
545 },
546
547 TransactionEvent::AccountStorageBeforeMapItemAccess {
548 active_account_id,
549 map_root,
550 map_key,
551 } => {
552 self.on_account_storage_map_witness_requested(
553 active_account_id,
554 map_root,
555 map_key,
556 )
557 .await
558 },
559
560 TransactionEvent::AccountAfterIncrementNonce => {
561 self.base_host.on_account_after_increment_nonce()
562 },
563
564 TransactionEvent::AccountPushProcedureIndex { code_commitment, procedure_root } => {
565 self.base_host.on_account_push_procedure_index(code_commitment, procedure_root)
566 },
567
568 TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => {
569 match recipient_data {
570 RecipientData::Digest(recipient_digest) => {
571 self.base_host.output_note_from_recipient_digest(
572 note_idx,
573 metadata,
574 recipient_digest,
575 )
576 },
577 RecipientData::Recipient(note_recipient) => self
578 .base_host
579 .output_note_from_recipient(note_idx, metadata, note_recipient),
580 RecipientData::ScriptMissing {
581 recipient_digest,
582 serial_num,
583 script_root,
584 note_inputs,
585 } => {
586 self.on_note_script_requested(
587 note_idx,
588 recipient_digest,
589 script_root,
590 metadata,
591 note_inputs,
592 serial_num,
593 )
594 .await
595 },
596 }
597 },
598
599 TransactionEvent::NoteBeforeAddAsset { note_idx, asset } => {
600 self.base_host.on_note_before_add_asset(note_idx, asset)
601 },
602
603 TransactionEvent::NoteBeforeSetAttachment { note_idx, attachment } => self
604 .base_host
605 .on_note_before_set_attachment(note_idx, attachment)
606 .map(|_| Vec::new()),
607
608 TransactionEvent::AuthRequest { pub_key_hash, tx_summary, signature } => {
609 if let Some(signature) = signature {
610 Ok(self.base_host.on_auth_requested(signature))
611 } else {
612 self.on_auth_requested(pub_key_hash, tx_summary).await
613 }
614 },
615
616 TransactionEvent::Unauthorized { tx_summary } => {
618 Err(TransactionKernelError::Unauthorized(Box::new(tx_summary)))
619 },
620
621 TransactionEvent::EpilogueBeforeTxFeeRemovedFromAccount { fee_asset } => {
622 self.on_before_tx_fee_removed_from_account(fee_asset).await
623 },
624
625 TransactionEvent::LinkMapSet { advice_mutation } => Ok(advice_mutation),
626 TransactionEvent::LinkMapGet { advice_mutation } => Ok(advice_mutation),
627 TransactionEvent::Progress(tx_progress) => match tx_progress {
628 TransactionProgressEvent::PrologueStart(clk) => {
629 self.tx_progress.start_prologue(clk);
630 Ok(Vec::new())
631 },
632 TransactionProgressEvent::PrologueEnd(clk) => {
633 self.tx_progress.end_prologue(clk);
634 Ok(Vec::new())
635 },
636 TransactionProgressEvent::NotesProcessingStart(clk) => {
637 self.tx_progress.start_notes_processing(clk);
638 Ok(Vec::new())
639 },
640 TransactionProgressEvent::NotesProcessingEnd(clk) => {
641 self.tx_progress.end_notes_processing(clk);
642 Ok(Vec::new())
643 },
644 TransactionProgressEvent::NoteExecutionStart { note_id, clk } => {
645 self.tx_progress.start_note_execution(clk, note_id);
646 Ok(Vec::new())
647 },
648 TransactionProgressEvent::NoteExecutionEnd(clk) => {
649 self.tx_progress.end_note_execution(clk);
650 Ok(Vec::new())
651 },
652 TransactionProgressEvent::TxScriptProcessingStart(clk) => {
653 self.tx_progress.start_tx_script_processing(clk);
654 Ok(Vec::new())
655 },
656 TransactionProgressEvent::TxScriptProcessingEnd(clk) => {
657 self.tx_progress.end_tx_script_processing(clk);
658 Ok(Vec::new())
659 },
660 TransactionProgressEvent::EpilogueStart(clk) => {
661 self.tx_progress.start_epilogue(clk);
662 Ok(Vec::new())
663 },
664 TransactionProgressEvent::EpilogueEnd(clk) => {
665 self.tx_progress.end_epilogue(clk);
666 Ok(Vec::new())
667 },
668 TransactionProgressEvent::EpilogueAuthProcStart(clk) => {
669 self.tx_progress.start_auth_procedure(clk);
670 Ok(Vec::new())
671 },
672 TransactionProgressEvent::EpilogueAuthProcEnd(clk) => {
673 self.tx_progress.end_auth_procedure(clk);
674 Ok(Vec::new())
675 },
676 TransactionProgressEvent::EpilogueAfterTxCyclesObtained(clk) => {
677 self.tx_progress.epilogue_after_tx_cycles_obtained(clk);
678 Ok(Vec::new())
679 },
680 },
681 };
682
683 result.map_err(EventError::from)
684 }
685 }
686}
687
688fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> [AdviceMutation; 2] {
694 let merkle_store_ext = AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes());
696
697 let smt_proof = SmtProof::from(asset_witness);
698 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
699 smt_proof.leaf().hash(),
700 smt_proof.leaf().to_elements(),
701 )]));
702
703 [merkle_store_ext, map_ext]
704}