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