use alloc::vec::Vec;
use core::fmt::Debug;
use miden_core::utils::{Deserializable, Serializable};
use super::PartialBlockchain;
use crate::TransactionInputError;
use crate::account::{AccountCode, PartialAccount};
use crate::block::{BlockHeader, BlockNumber};
use crate::note::{Note, NoteInclusionProof};
use crate::transaction::{TransactionArgs, TransactionScript};
mod account;
pub use account::AccountInputs;
mod notes;
use miden_processor::AdviceInputs;
pub use notes::{InputNote, InputNotes, ToInputNoteCommitments};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransactionInputs {
account: PartialAccount,
block_header: BlockHeader,
blockchain: PartialBlockchain,
input_notes: InputNotes<InputNote>,
tx_args: TransactionArgs,
advice_inputs: AdviceInputs,
foreign_account_code: Vec<AccountCode>,
}
impl TransactionInputs {
pub fn new(
account: PartialAccount,
block_header: BlockHeader,
blockchain: PartialBlockchain,
input_notes: InputNotes<InputNote>,
) -> Result<Self, TransactionInputError> {
if blockchain.chain_length() != block_header.block_num() {
return Err(TransactionInputError::InconsistentChainLength {
expected: block_header.block_num(),
actual: blockchain.chain_length(),
});
}
if blockchain.peaks().hash_peaks() != block_header.chain_commitment() {
return Err(TransactionInputError::InconsistentChainCommitment {
expected: block_header.chain_commitment(),
actual: blockchain.peaks().hash_peaks(),
});
}
for note in input_notes.iter() {
if let InputNote::Authenticated { note, proof } = note {
let note_block_num = proof.location().block_num();
let block_header = if note_block_num == block_header.block_num() {
&block_header
} else {
blockchain.get_block(note_block_num).ok_or(
TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
)?
};
validate_is_in_block(note, proof, block_header)?;
}
}
Ok(Self {
account,
block_header,
blockchain,
input_notes,
tx_args: TransactionArgs::default(),
advice_inputs: AdviceInputs::default(),
foreign_account_code: Vec::new(),
})
}
pub fn with_foreign_account_code(mut self, foreign_account_code: Vec<AccountCode>) -> Self {
self.foreign_account_code = foreign_account_code;
self
}
pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
self.set_tx_args_inner(tx_args);
self
}
pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
self.set_advice_inputs(advice_inputs);
self
}
pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
self.input_notes = new_notes.into();
}
pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
let AdviceInputs { map, store, .. } = new_advice_inputs;
self.advice_inputs = AdviceInputs { stack: Default::default(), map, store };
self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
}
#[cfg(feature = "testing")]
pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
self.set_tx_args_inner(tx_args);
}
pub fn account(&self) -> &PartialAccount {
&self.account
}
pub fn block_header(&self) -> &BlockHeader {
&self.block_header
}
pub fn blockchain(&self) -> &PartialBlockchain {
&self.blockchain
}
pub fn input_notes(&self) -> &InputNotes<InputNote> {
&self.input_notes
}
pub fn ref_block(&self) -> BlockNumber {
self.block_header.block_num()
}
pub fn tx_script(&self) -> Option<&TransactionScript> {
self.tx_args.tx_script()
}
pub fn foreign_account_code(&self) -> &[AccountCode] {
&self.foreign_account_code
}
pub fn advice_inputs(&self) -> &AdviceInputs {
&self.advice_inputs
}
pub fn tx_args(&self) -> &TransactionArgs {
&self.tx_args
}
pub fn into_parts(
self,
) -> (
PartialAccount,
BlockHeader,
PartialBlockchain,
InputNotes<InputNote>,
TransactionArgs,
) {
(self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
}
fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
self.tx_args = tx_args;
self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
}
}
impl Serializable for TransactionInputs {
fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
self.account.write_into(target);
self.block_header.write_into(target);
self.blockchain.write_into(target);
self.input_notes.write_into(target);
self.tx_args.write_into(target);
self.advice_inputs.write_into(target);
self.foreign_account_code.write_into(target);
}
}
impl Deserializable for TransactionInputs {
fn read_from<R: miden_core::utils::ByteReader>(
source: &mut R,
) -> Result<Self, miden_core::utils::DeserializationError> {
let account = PartialAccount::read_from(source)?;
let block_header = BlockHeader::read_from(source)?;
let blockchain = PartialBlockchain::read_from(source)?;
let input_notes = InputNotes::read_from(source)?;
let tx_args = TransactionArgs::read_from(source)?;
let advice_inputs = AdviceInputs::read_from(source)?;
let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
Ok(TransactionInputs {
account,
block_header,
blockchain,
input_notes,
tx_args,
advice_inputs,
foreign_account_code,
})
}
}
fn validate_is_in_block(
note: &Note,
proof: &NoteInclusionProof,
block_header: &BlockHeader,
) -> Result<(), TransactionInputError> {
let note_index = proof.location().node_index_in_block().into();
let note_commitment = note.commitment();
proof
.note_path()
.verify(note_index, note_commitment, &block_header.note_root())
.map_err(|_| {
TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
})
}