Skip to main content

miden_node_store/state/view/
batch_inputs.rs

1//! Batch input query for the block producer.
2//!
3//! Combines in-memory snapshot data (partial MMR) with database lookups, scoping the latter by
4//! the view's tip so both sources describe the same block height.
5
6use std::collections::BTreeSet;
7
8use miden_node_proto::domain::batch::BatchInputs;
9use miden_protocol::Word;
10use miden_protocol::block::BlockNumber;
11use miden_protocol::transaction::PartialBlockchain;
12
13use super::StateView;
14use crate::errors::GetBatchInputsError;
15
16impl StateView {
17    /// Fetches the inputs for a transaction batch from the database.
18    ///
19    /// ## Inputs
20    ///
21    /// The function takes as input:
22    /// - The tx reference blocks are the set of blocks referenced by transactions in the batch.
23    /// - The unauthenticated note commitments are the set of commitments of unauthenticated notes
24    ///   consumed by all transactions in the batch. For these notes, we attempt to find inclusion
25    ///   proofs. Not all notes will exist in the DB necessarily, as some notes can be created and
26    ///   consumed within the same batch.
27    ///
28    /// ## Outputs
29    ///
30    /// The function will return:
31    /// - A block inclusion proof for all tx reference blocks and for all blocks which are
32    ///   referenced by a note inclusion proof.
33    /// - Note inclusion proofs for all notes that were found in the DB.
34    /// - The block header that the batch should reference, i.e. the latest known block.
35    pub async fn get_batch_inputs(
36        &self,
37        tx_reference_blocks: BTreeSet<BlockNumber>,
38        unauthenticated_note_commitments: BTreeSet<Word>,
39    ) -> Result<BatchInputs, GetBatchInputsError> {
40        if tx_reference_blocks.is_empty() {
41            return Err(GetBatchInputsError::TransactionBlockReferencesEmpty);
42        }
43
44        let latest_block_num = self.tip();
45
46        // First we grab note inclusion proofs for the known notes. These proofs only prove that the
47        // note was included in a given block. We then also need to prove that each of those blocks
48        // is included in the chain. The proofs are scoped by the view's tip, so the database cannot
49        // report a note from a block the pinned snapshot cannot prove yet.
50        let note_proofs = self
51            .db
52            .select_note_inclusion_proofs(unauthenticated_note_commitments, latest_block_num)
53            .await
54            .map_err(GetBatchInputsError::SelectNoteInclusionProofError)?;
55
56        // The set of blocks that the notes are included in.
57        let note_blocks = note_proofs.values().map(|proof| proof.location().block_num());
58
59        // Collect all blocks we need to query without duplicates, which is:
60        // - all blocks for which we need to prove note inclusion.
61        // - all blocks referenced by transactions in the batch.
62        let mut blocks: BTreeSet<BlockNumber> = tx_reference_blocks;
63        blocks.extend(note_blocks);
64
65        // Remove the latest block from the to-be-tracked blocks as it will be the reference block
66        // for the batch itself and thus added to the MMR within the batch kernel, so there is no
67        // need to prove its inclusion.
68        blocks.remove(&*latest_block_num);
69
70        // Scoping the blocks doubles as the validation that none lies beyond the view's tip. Scoped
71        // in descending order, so the first failure carries the highest block number.
72        let scoped_blocks = blocks
73            .iter()
74            .rev()
75            .map(|&block| {
76                self.scope_block(block).ok_or(
77                    GetBatchInputsError::UnknownTransactionBlockReference {
78                        highest_block_num: block,
79                        latest_block_num: *latest_block_num,
80                    },
81                )
82            })
83            .collect::<Result<Vec<_>, _>>()?;
84
85        // SAFETY:
86        // - The latest block num was retrieved from the view's blockchain from which we will
87        //   also retrieve the proofs, so it is guaranteed to exist in that chain.
88        // - Scoping above proved that no block in the set is greater than the latest block number
89        //   *and* the latest block num was removed from the set. Therefore only block numbers
90        //   smaller than latest block num remain in the set. Therefore all the block numbers are
91        //   guaranteed to exist in the chain state at latest block num.
92        let partial_mmr =
93            self.blockchain().partial_mmr_from_blocks(&blocks, *latest_block_num).expect(
94                "latest block num should exist and all blocks in set should be < than latest block",
95            );
96
97        // Fetch the reference block of the batch as part of this query, so we can avoid looking it
98        // up in a separate DB access.
99        let mut headers = self
100            .db
101            .select_block_headers(
102                scoped_blocks.into_iter().chain(std::iter::once(latest_block_num)),
103            )
104            .await
105            .map_err(GetBatchInputsError::SelectBlockHeaderError)?;
106
107        // Find and remove the batch reference block as we don't want to add it to the chain MMR.
108        let header_index = headers
109            .iter()
110            .enumerate()
111            .find_map(|(index, header)| (header.block_num() == *latest_block_num).then_some(index))
112            .expect("DB should have returned the header of the batch reference block");
113
114        // The order doesn't matter for PartialBlockchain::new, so swap remove is fine.
115        let batch_reference_block_header = headers.swap_remove(header_index);
116
117        // SAFETY: This should not error because:
118        // - we're passing exactly the block headers that we've added to the partial MMR,
119        // - so none of the block headers block numbers should exceed the chain length of the
120        //   partial MMR,
121        // - and we've added blocks to a BTreeSet, so there can be no duplicates.
122        //
123        // We construct headers and partial MMR in concert, so they are consistent. This is why we
124        // can call the unchecked constructor.
125        let partial_block_chain = PartialBlockchain::new_unchecked(partial_mmr, headers)
126            .expect("partial mmr and block headers should be consistent");
127
128        Ok(BatchInputs {
129            batch_reference_block_header,
130            note_proofs,
131            partial_block_chain,
132        })
133    }
134}