1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3use core::fmt::Debug;
4
5use miden_crypto::merkle::NodeIndex;
6use miden_crypto::merkle::smt::{SmtLeaf, SmtProof};
7
8use super::PartialBlockchain;
9use crate::Word;
10use crate::account::{
11 AccountCode,
12 AccountHeader,
13 AccountId,
14 AccountStorageHeader,
15 PartialAccount,
16 PartialStorage,
17 StorageMapKey,
18 StorageMapWitness,
19 StorageSlotId,
20 StorageSlotName,
21};
22use crate::asset::{Asset, AssetId, AssetWitness, PartialVault};
23use crate::block::account_tree::{AccountIdKey, AccountWitness};
24use crate::block::{BlockHeader, BlockNumber};
25use crate::crypto::merkle::SparseMerklePath;
26use crate::errors::{TransactionInputError, TransactionInputsExtractionError};
27use crate::note::{Note, NoteInclusionProof};
28use crate::protocol_config::ProtocolConfig;
29use crate::transaction::{TransactionArgs, TransactionScript};
30use crate::utils::serde::{
31 ByteReader,
32 ByteWriter,
33 Deserializable,
34 DeserializationError,
35 Serializable,
36};
37
38#[cfg(test)]
39mod tests;
40
41mod account;
42pub use account::AccountInputs;
43
44mod notes;
45pub use notes::{InputNote, InputNotes, ToInputNoteCommitments};
46
47use crate::vm::AdviceInputs;
48
49#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct TransactionInputs {
55 account: PartialAccount,
56 block_header: BlockHeader,
57 protocol_config: ProtocolConfig,
59 blockchain: PartialBlockchain,
60 input_notes: InputNotes<InputNote>,
61 tx_args: TransactionArgs,
62 advice_inputs: AdviceInputs,
63 foreign_account_code: Vec<AccountCode>,
64 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
66}
67
68impl TransactionInputs {
69 pub fn new(
81 account: PartialAccount,
82 block_header: BlockHeader,
83 protocol_config: ProtocolConfig,
84 blockchain: PartialBlockchain,
85 input_notes: InputNotes<InputNote>,
86 ) -> Result<Self, TransactionInputError> {
87 Self::try_from_parts(
88 account,
89 block_header,
90 protocol_config,
91 blockchain,
92 input_notes,
93 TransactionArgs::default(),
94 AdviceInputs::default(),
95 Vec::new(),
96 BTreeMap::new(),
97 )
98 }
99
100 #[allow(clippy::too_many_arguments)]
106 pub fn try_from_parts(
107 account: PartialAccount,
108 block_header: BlockHeader,
109 protocol_config: ProtocolConfig,
110 blockchain: PartialBlockchain,
111 input_notes: InputNotes<InputNote>,
112 tx_args: TransactionArgs,
113 advice_inputs: AdviceInputs,
114 foreign_account_code: Vec<AccountCode>,
115 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
116 ) -> Result<Self, TransactionInputError> {
117 Self::validate(&block_header, &protocol_config, &blockchain, &input_notes)?;
118
119 Ok(Self {
120 account,
121 block_header,
122 protocol_config,
123 blockchain,
124 input_notes,
125 tx_args,
126 advice_inputs,
127 foreign_account_code,
128 foreign_account_slot_names,
129 })
130 }
131
132 fn validate(
136 block_header: &BlockHeader,
137 protocol_config: &ProtocolConfig,
138 blockchain: &PartialBlockchain,
139 input_notes: &InputNotes<InputNote>,
140 ) -> Result<(), TransactionInputError> {
141 let protocol_config_commitment = protocol_config.to_commitment();
143 if protocol_config_commitment != block_header.protocol_config_commitment() {
144 return Err(TransactionInputError::InconsistentProtocolConfig {
145 expected: block_header.protocol_config_commitment(),
146 actual: protocol_config_commitment,
147 });
148 }
149 if blockchain.chain_length() != block_header.block_num() {
151 return Err(TransactionInputError::InconsistentChainLength {
152 expected: block_header.block_num(),
153 actual: blockchain.chain_length(),
154 });
155 }
156 if blockchain.peaks().hash_peaks() != block_header.chain_commitment() {
157 return Err(TransactionInputError::InconsistentChainCommitment {
158 expected: block_header.chain_commitment(),
159 actual: blockchain.peaks().hash_peaks(),
160 });
161 }
162 for note in input_notes.iter() {
164 if let InputNote::Authenticated { note, proof } = note {
165 let note_block_num = proof.location().block_num();
166 let block_header = if note_block_num == block_header.block_num() {
167 block_header
168 } else {
169 blockchain.get_block(note_block_num).ok_or(
170 TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
171 )?
172 };
173 validate_is_in_block(note, proof, block_header)?;
174 }
175 }
176
177 Ok(())
178 }
179
180 pub fn with_asset_witnesses(mut self, witnesses: Vec<AssetWitness>) -> Self {
182 for witness in witnesses {
183 let leaf = witness.proof().leaf();
184 let witness_inputs = AdviceInputs::default()
185 .with_merkle_store(witness.authenticated_nodes().collect())
186 .with_map([(leaf.hash(), leaf.to_elements().collect())]);
187 self.advice_inputs.extend(witness_inputs);
188 }
189
190 self
191 }
192
193 pub fn with_foreign_account_code(mut self, foreign_account_code: Vec<AccountCode>) -> Self {
195 self.foreign_account_code = foreign_account_code;
196 self
197 }
198
199 pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
201 self.set_tx_args_inner(tx_args);
202 self
203 }
204
205 pub fn with_foreign_account_slot_names(
207 mut self,
208 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
209 ) -> Self {
210 self.foreign_account_slot_names = foreign_account_slot_names;
211 self
212 }
213
214 pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
216 self.set_advice_inputs(advice_inputs);
217 self
218 }
219
220 pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
225 self.input_notes = new_notes.into();
226 }
227
228 pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
232 let (_stack, map, store) = new_advice_inputs.into_parts();
233 self.advice_inputs = AdviceInputs::from(map).with_merkle_store(store);
234 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
235 }
236
237 #[cfg(feature = "testing")]
239 pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
240 self.set_tx_args_inner(tx_args);
241 }
242
243 pub fn account(&self) -> &PartialAccount {
248 &self.account
249 }
250
251 pub fn block_header(&self) -> &BlockHeader {
253 &self.block_header
254 }
255
256 pub fn protocol_config(&self) -> &ProtocolConfig {
258 &self.protocol_config
259 }
260
261 pub fn blockchain(&self) -> &PartialBlockchain {
264 &self.blockchain
265 }
266
267 pub fn input_notes(&self) -> &InputNotes<InputNote> {
269 &self.input_notes
270 }
271
272 pub fn ref_block(&self) -> BlockNumber {
274 self.block_header.block_num()
275 }
276
277 pub fn collect_block_commitments(&self) -> BTreeMap<BlockNumber, Word> {
282 let mut commitments: BTreeMap<BlockNumber, Word> = self
283 .blockchain
284 .block_headers()
285 .map(|header| (header.block_num(), header.commitment()))
286 .collect();
287 commitments.insert(self.ref_block(), self.block_header.commitment());
288
289 commitments
290 }
291
292 pub fn tx_script(&self) -> Option<&TransactionScript> {
294 self.tx_args.tx_script()
295 }
296
297 pub fn foreign_account_code(&self) -> &[AccountCode] {
299 &self.foreign_account_code
300 }
301
302 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
304 &self.foreign_account_slot_names
305 }
306
307 pub fn advice_inputs(&self) -> &AdviceInputs {
309 &self.advice_inputs
310 }
311
312 pub fn tx_args(&self) -> &TransactionArgs {
314 &self.tx_args
315 }
316
317 pub fn read_storage_map_witness(
322 &self,
323 map_root: Word,
324 map_key: StorageMapKey,
325 ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
326 let leaf_index = map_key.hash().to_leaf_index();
328
329 let merkle_path = self.advice_inputs.store().get_path(map_root, leaf_index.into())?;
331 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
332
333 let merkle_node = self.advice_inputs.store().get_node(map_root, leaf_index.into())?;
335 let smt_leaf_elements = self
336 .advice_inputs
337 .map()
338 .get(&merkle_node)
339 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
340 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, leaf_index)?;
341
342 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
344 let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
345
346 Ok(storage_witness)
347 }
348
349 pub fn read_vault_asset_witnesses(
357 &self,
358 vault_root: Word,
359 asset_ids: BTreeSet<AssetId>,
360 ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
361 let mut asset_witnesses = Vec::new();
362 for asset_id in asset_ids {
363 let smt_index = asset_id.hash().to_leaf_index();
364 let merkle_path = self.advice_inputs.store().get_path(vault_root, smt_index.into())?;
366 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
367
368 let merkle_node = self.advice_inputs.store().get_node(vault_root, smt_index.into())?;
370 let smt_leaf_elements = self
371 .advice_inputs
372 .map()
373 .get(&merkle_node)
374 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
375 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
376
377 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
379 let asset_witness = AssetWitness::new(smt_proof, [asset_id])?;
380 asset_witnesses.push(asset_witness);
381 }
382 Ok(asset_witnesses)
383 }
384
385 pub fn has_vault_asset_witness(&self, vault_root: Word, asset_id: &AssetId) -> bool {
390 let smt_index: NodeIndex = asset_id.hash().to_leaf_index().into();
391
392 if !self.advice_inputs.store().has_path(vault_root, smt_index) {
394 return false;
395 }
396
397 match self.advice_inputs.store().get_node(vault_root, smt_index) {
399 Ok(node) => self.advice_inputs.map().contains_key(&node),
400 Err(_) => false,
401 }
402 }
403
404 pub fn read_vault_asset(
414 &self,
415 vault_root: Word,
416 asset_id: AssetId,
417 ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
418 let witnesses =
419 self.read_vault_asset_witnesses(vault_root, BTreeSet::from_iter([asset_id]))?;
420 let witness = witnesses
421 .into_iter()
422 .next()
423 .expect("one key requested should yield exactly one witness");
424 Ok(witness.find(asset_id))
425 }
426
427 pub fn read_foreign_account_inputs(
434 &self,
435 account_id: AccountId,
436 ) -> Result<AccountInputs, TransactionInputsExtractionError> {
437 if account_id == self.account().id() {
438 return Err(TransactionInputsExtractionError::AccountNotForeign);
439 }
440
441 let account_id_key = AccountIdKey::from(account_id);
443 let header_elements = self
444 .advice_inputs
445 .map()
446 .get(&account_id_key.as_word())
447 .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
448
449 let header = AccountHeader::try_from_elements(header_elements)?;
451
452 let partial_account = self.read_foreign_partial_account(&header)?;
454 let witness = self.read_foreign_account_witness(&header)?;
455 Ok(AccountInputs::new(partial_account, witness))
456 }
457
458 fn read_foreign_partial_account(
461 &self,
462 header: &AccountHeader,
463 ) -> Result<PartialAccount, TransactionInputsExtractionError> {
464 let partial_vault = PartialVault::new(header.vault_root());
466
467 let account_code = self
469 .foreign_account_code
470 .iter()
471 .find(|code| code.commitment() == header.code_commitment())
472 .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
473 .clone();
474
475 let storage_header_elements = self
477 .advice_inputs
478 .map()
479 .get(&header.storage_commitment())
480 .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
481
482 let storage_header = AccountStorageHeader::try_from_elements(
484 storage_header_elements,
485 self.foreign_account_slot_names(),
486 )?;
487
488 let partial_storage = PartialStorage::new(storage_header, [])?;
490
491 let partial_account = PartialAccount::new(
493 header.id(),
494 header.nonce(),
495 account_code,
496 partial_storage,
497 partial_vault,
498 None, )?;
500
501 Ok(partial_account)
502 }
503
504 fn read_foreign_account_witness(
507 &self,
508 header: &AccountHeader,
509 ) -> Result<AccountWitness, TransactionInputsExtractionError> {
510 let account_tree_root = self.block_header.account_root();
512 let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
513
514 let merkle_path = self.advice_inputs.store().get_path(account_tree_root, leaf_index)?;
516
517 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
519
520 let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
522
523 Ok(witness)
524 }
525
526 pub fn into_parts(
531 self,
532 ) -> (
533 PartialAccount,
534 BlockHeader,
535 PartialBlockchain,
536 InputNotes<InputNote>,
537 TransactionArgs,
538 ) {
539 (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
540 }
541
542 fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
550 self.tx_args = tx_args;
551 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
552 }
553}
554
555impl Serializable for TransactionInputs {
559 fn write_into<W: ByteWriter>(&self, target: &mut W) {
560 self.account.write_into(target);
561 self.block_header.write_into(target);
562 self.protocol_config.write_into(target);
563 self.blockchain.write_into(target);
564 self.input_notes.write_into(target);
565 self.tx_args.write_into(target);
566 self.advice_inputs.write_into(target);
567 self.foreign_account_code.write_into(target);
568 self.foreign_account_slot_names.write_into(target);
569 }
570}
571
572impl Deserializable for TransactionInputs {
573 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
574 let account = PartialAccount::read_from(source)?;
575 let block_header = BlockHeader::read_from(source)?;
576 let protocol_config = ProtocolConfig::read_from(source)?;
577 let blockchain = PartialBlockchain::read_from(source)?;
578 let input_notes = InputNotes::read_from(source)?;
579 let tx_args = TransactionArgs::read_from(source)?;
580 let advice_inputs = AdviceInputs::read_from(source)?;
581 let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
582 let foreign_account_slot_names =
583 BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
584
585 Ok(TransactionInputs {
586 account,
587 block_header,
588 protocol_config,
589 blockchain,
590 input_notes,
591 tx_args,
592 advice_inputs,
593 foreign_account_code,
594 foreign_account_slot_names,
595 })
596 }
597}
598
599fn validate_is_in_block(
604 note: &Note,
605 proof: &NoteInclusionProof,
606 block_header: &BlockHeader,
607) -> Result<(), TransactionInputError> {
608 let note_index = proof.location().block_note_tree_index().into();
609 let note_id = note.id().as_word();
610 proof
611 .note_path()
612 .verify(note_index, note_id, &block_header.note_root())
613 .map_err(|_| {
614 TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
615 })
616}