Skip to main content

miden_node_store/state/view/
transaction_inputs.rs

1//! Transaction input query for the block producer's transaction validation.
2
3use std::collections::HashSet;
4use std::ops::ControlFlow;
5
6use miden_node_tracing::miden_instrument;
7use miden_protocol::Word;
8use miden_protocol::account::AccountId;
9use miden_protocol::note::{NoteId, Nullifier};
10
11use super::StateView;
12use crate::COMPONENT;
13use crate::db::NullifierInfo;
14use crate::errors::DatabaseError;
15
16/// Store-level inputs for validating a proven transaction.
17#[derive(Debug, Default)]
18pub struct TransactionInputs {
19    pub account_commitment: Word,
20    pub nullifiers: Vec<NullifierInfo>,
21    pub found_unauthenticated_notes: HashSet<NoteId>,
22    pub new_account_id_prefix_is_unique: Option<bool>,
23}
24
25impl StateView {
26    /// Returns data needed by the block producer to verify transaction validity.
27    #[miden_instrument(
28        target = COMPONENT,
29        fields(
30            account.id = account_id,
31            note.nullifiers = nullifiers,
32        ),
33    )]
34    pub async fn get_transaction_inputs(
35        &self,
36        account_id: AccountId,
37        nullifiers: &[Nullifier],
38        unauthenticated_note_ids: Vec<NoteId>,
39    ) -> Result<TransactionInputs, DatabaseError> {
40        let tree_inputs = self.with_inner_read_blocking(|inner| {
41            let account_commitment = inner.account_tree.get_latest_commitment(account_id);
42
43            let new_account_id_prefix_is_unique = if account_commitment.is_empty() {
44                Some(!inner.account_tree.contains_account_id_prefix_in_latest(account_id.prefix()))
45            } else {
46                None
47            };
48
49            // Non-unique account Id prefixes for new accounts are not allowed, so the transaction
50            // cannot be valid and the response is already complete.
51            if let Some(false) = new_account_id_prefix_is_unique {
52                return ControlFlow::Break(TransactionInputs {
53                    new_account_id_prefix_is_unique,
54                    ..Default::default()
55                });
56            }
57
58            let nullifiers = nullifiers
59                .iter()
60                .map(|nullifier| NullifierInfo {
61                    nullifier: *nullifier,
62                    block_num: inner.nullifier_tree.get_block_num(nullifier).unwrap_or_default(),
63                })
64                .collect();
65
66            ControlFlow::Continue((account_commitment, nullifiers, new_account_id_prefix_is_unique))
67        });
68        // `Break` carries a complete response (duplicate account ID prefix), so it is returned
69        // as-is without the note lookup below; `Continue` carries the tree reads needed to build
70        // the full response.
71        let (account_commitment, nullifiers, new_account_id_prefix_is_unique) = match tree_inputs {
72            ControlFlow::Continue(inputs) => inputs,
73            ControlFlow::Break(response) => return Ok(response),
74        };
75
76        // Scope the note lookup by the view's tip so the result is consistent with the tree reads
77        // above: mid-apply, the DB may already contain notes from a block the snapshot does not
78        // include yet.
79        let found_unauthenticated_notes =
80            self.db.select_existing_note_ids(unauthenticated_note_ids, self.tip()).await?;
81
82        Ok(TransactionInputs {
83            account_commitment,
84            nullifiers,
85            found_unauthenticated_notes,
86            new_account_id_prefix_is_unique,
87        })
88    }
89}