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 message = signing_inputs.to_commitment();
189
190 let signature: Vec<Felt> = authenticator
191 .get_signature(PublicKeyCommitment::from(pub_key_hash), &signing_inputs)
192 .await
193 .map_err(TransactionKernelError::SignatureGenerationFailed)?
194 .to_prepared_signature(message);
195
196 let signature_key = Hasher::merge(&[pub_key_hash, message]);
197 self.generated_signatures.insert(signature_key, signature.clone());
198
199 Ok(vec![AdviceMutation::extend_stack(signature)])
200 }
201
202 async fn on_before_tx_fee_removed_from_account(
205 &self,
206 fee_asset: FungibleAsset,
207 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
208 let asset_witness = self
209 .base_host
210 .store()
211 .get_vault_asset_witness(
212 self.base_host.initial_account_header().id(),
213 self.base_host.initial_account_header().vault_root(),
214 fee_asset.vault_key(),
215 )
216 .await
217 .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
218 vault_root: self.base_host.initial_account_header().vault_root(),
219 asset_key: fee_asset.vault_key(),
220 source: err,
221 })?;
222
223 let initial_fee_asset = asset_witness
225 .find(fee_asset.vault_key())
226 .and_then(|asset| match asset {
227 Asset::Fungible(fungible_asset) => Some(fungible_asset),
228 _ => None,
229 })
230 .unwrap_or(
231 FungibleAsset::new(fee_asset.faucet_id(), 0)
232 .expect("fungible asset created from fee asset should be valid"),
233 );
234
235 let current_fee_asset = {
238 let fee_asset_amount_delta = self
239 .base_host
240 .account_delta_tracker()
241 .vault_delta()
242 .fungible()
243 .amount(&initial_fee_asset.faucet_id())
244 .unwrap_or(0);
245
246 let fee_asset_delta = FungibleAsset::new(
249 initial_fee_asset.faucet_id(),
250 fee_asset_amount_delta.unsigned_abs(),
251 )
252 .expect("faucet ID and amount should be valid");
253
254 if fee_asset_amount_delta > 0 {
257 initial_fee_asset
258 .add(fee_asset_delta)
259 .expect("transaction kernel should ensure amounts do not exceed MAX_AMOUNT")
260 } else {
261 initial_fee_asset
262 .sub(fee_asset_delta)
263 .expect("transaction kernel should ensure amount is not negative")
264 }
265 };
266
267 if current_fee_asset.amount() < fee_asset.amount() {
269 return Err(TransactionKernelError::InsufficientFee {
270 account_balance: current_fee_asset.amount(),
271 tx_fee: fee_asset.amount(),
272 });
273 }
274
275 Ok(asset_witness_to_advice_mutation(asset_witness))
276 }
277
278 async fn on_account_storage_map_witness_requested(
283 &self,
284 current_account_id: AccountId,
285 map_root: Word,
286 map_key: Word,
287 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
288 let storage_map_witness = self
289 .base_host
290 .store()
291 .get_storage_map_witness(current_account_id, map_root, map_key)
292 .await
293 .map_err(|err| TransactionKernelError::GetStorageMapWitness {
294 map_root,
295 map_key,
296 source: err,
297 })?;
298
299 let merkle_store_ext =
301 AdviceMutation::extend_merkle_store(storage_map_witness.authenticated_nodes());
302
303 let smt_proof = SmtProof::from(storage_map_witness);
304 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
305 smt_proof.leaf().hash(),
306 smt_proof.leaf().to_elements(),
307 )]));
308
309 Ok(vec![merkle_store_ext, map_ext])
310 }
311
312 async fn on_account_vault_asset_witness_requested(
345 &self,
346 current_account_id: AccountId,
347 vault_root: Word,
348 asset_key: AssetVaultKey,
349 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
350 let asset_witness = self
351 .base_host
352 .store()
353 .get_vault_asset_witness(current_account_id, vault_root, asset_key)
354 .await
355 .map_err(|err| TransactionKernelError::GetVaultAssetWitness {
356 vault_root,
357 asset_key,
358 source: err,
359 })?;
360
361 Ok(asset_witness_to_advice_mutation(asset_witness))
362 }
363
364 async fn on_note_script_requested(
370 &mut self,
371 script_root: Word,
372 metadata: NoteMetadata,
373 recipient_digest: Word,
374 note_idx: usize,
375 note_inputs: NoteInputs,
376 serial_num: Word,
377 ) -> Result<Vec<AdviceMutation>, TransactionKernelError> {
378 let note_script_result = self.base_host.store().get_note_script(script_root).await;
379
380 let (recipient, mutations) = match note_script_result {
381 Ok(note_script) => {
382 let script_felts: Vec<Felt> = (¬e_script).into();
383 let recipient = NoteRecipient::new(serial_num, note_script, note_inputs);
384 let mutations = vec![AdviceMutation::extend_map(AdviceMap::from_iter([(
385 script_root,
386 script_felts,
387 )]))];
388
389 (Some(recipient), mutations)
390 },
391 Err(DataStoreError::NoteScriptNotFound(_)) if metadata.is_private() => {
392 (None, Vec::new())
393 },
394 Err(DataStoreError::NoteScriptNotFound(_)) => {
395 return Err(TransactionKernelError::other(format!(
396 "note script with root {script_root} not found in data store for public note"
397 )));
398 },
399 Err(err) => {
400 return Err(TransactionKernelError::other_with_source(
401 "failed to retrieve note script from data store",
402 err,
403 ));
404 },
405 };
406
407 let note_builder = OutputNoteBuilder::new(metadata, recipient_digest, recipient)?;
408 self.base_host.insert_output_note_builder(note_idx, note_builder)?;
409
410 Ok(mutations)
411 }
412
413 #[allow(clippy::type_complexity)]
416 pub fn into_parts(
417 self,
418 ) -> (
419 AccountDelta,
420 InputNotes<InputNote>,
421 Vec<OutputNote>,
422 Vec<AccountCode>,
423 BTreeMap<Word, Vec<Felt>>,
424 TransactionProgress,
425 ) {
426 let (account_delta, input_notes, output_notes, tx_progress) = self.base_host.into_parts();
427
428 (
429 account_delta,
430 input_notes,
431 output_notes,
432 self.accessed_foreign_account_code,
433 self.generated_signatures,
434 tx_progress,
435 )
436 }
437}
438
439impl<STORE, AUTH> BaseHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
443where
444 STORE: DataStore,
445 AUTH: TransactionAuthenticator,
446{
447 fn get_label_and_source_file(
448 &self,
449 location: &Location,
450 ) -> (SourceSpan, Option<Arc<SourceFile>>) {
451 let source_manager = self.source_manager.as_ref();
452 let maybe_file = source_manager.get_by_uri(location.uri());
453 let span = source_manager.location_to_span(location.clone()).unwrap_or_default();
454 (span, maybe_file)
455 }
456}
457
458impl<STORE, AUTH> AsyncHost for TransactionExecutorHost<'_, '_, STORE, AUTH>
459where
460 STORE: DataStore + Sync,
461 AUTH: TransactionAuthenticator + Sync,
462{
463 fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
464 let mast_forest = self.base_host.get_mast_forest(node_digest);
465 async move { mast_forest }
466 }
467
468 fn on_event(
469 &mut self,
470 process: &ProcessState,
471 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
472 let event_id = EventId::from_felt(process.get_stack_item(0));
473
474 let event_handling_result = self.base_host.handle_event(process, event_id);
477
478 async move {
479 let event_handling = event_handling_result?;
480 let event_data = match event_handling {
481 TransactionEventHandling::Unhandled(event) => event,
482 TransactionEventHandling::Handled(mutations) => {
483 return Ok(mutations);
484 },
485 };
486
487 match event_data {
488 TransactionEventData::AuthRequest { pub_key_hash, signing_inputs } => self
489 .on_auth_requested(pub_key_hash, signing_inputs)
490 .await
491 .map_err(EventError::from),
492 TransactionEventData::TransactionFeeComputed { fee_asset } => self
493 .on_before_tx_fee_removed_from_account(fee_asset)
494 .await
495 .map_err(EventError::from),
496 TransactionEventData::ForeignAccount { account_id } => {
497 self.on_foreign_account_requested(account_id).await.map_err(EventError::from)
498 },
499 TransactionEventData::AccountVaultAssetWitness {
500 current_account_id,
501 vault_root,
502 asset_key,
503 } => self
504 .on_account_vault_asset_witness_requested(
505 current_account_id,
506 vault_root,
507 asset_key,
508 )
509 .await
510 .map_err(EventError::from),
511 TransactionEventData::AccountStorageMapWitness {
512 current_account_id,
513 map_root,
514 map_key,
515 } => self
516 .on_account_storage_map_witness_requested(current_account_id, map_root, map_key)
517 .await
518 .map_err(EventError::from),
519 TransactionEventData::NoteData {
520 note_idx,
521 metadata,
522 script_root,
523 recipient_digest,
524 note_inputs,
525 serial_num,
526 } => self
527 .on_note_script_requested(
528 script_root,
529 metadata,
530 recipient_digest,
531 note_idx,
532 note_inputs,
533 serial_num,
534 )
535 .await
536 .map_err(EventError::from),
537 }
538 }
539 }
540}
541
542fn asset_witness_to_advice_mutation(asset_witness: AssetWitness) -> Vec<AdviceMutation> {
548 let merkle_store_ext = AdviceMutation::extend_merkle_store(asset_witness.authenticated_nodes());
550
551 let smt_proof = SmtProof::from(asset_witness);
552 let map_ext = AdviceMutation::extend_map(AdviceMap::from_iter([(
553 smt_proof.leaf().hash(),
554 smt_proof.leaf().to_elements(),
555 )]));
556
557 vec![merkle_store_ext, map_ext]
558}