Skip to main content

miden_node_store/state/view/
block_inputs.rs

1//! Block input query for the block producer.
2//!
3//! Combines in-memory snapshot data (tree witnesses, partial MMR) with database lookups, scoping
4//! the latter by the view's tip so both sources describe the same block height.
5
6use std::collections::{BTreeMap, BTreeSet};
7
8use miden_protocol::Word;
9use miden_protocol::account::AccountId;
10use miden_protocol::block::account_tree::AccountWitness;
11use miden_protocol::block::nullifier_tree::NullifierWitness;
12use miden_protocol::block::{BlockInputs, BlockNumber};
13use miden_protocol::crypto::merkle::mmr::PartialMmr;
14use miden_protocol::note::Nullifier;
15use miden_protocol::transaction::PartialBlockchain;
16
17use super::{ScopedBlockNum, StateView};
18use crate::errors::GetBlockInputsError;
19
20type BlockInputWitnesses = (
21    ScopedBlockNum,
22    Vec<ScopedBlockNum>,
23    BTreeMap<AccountId, AccountWitness>,
24    BTreeMap<Nullifier, NullifierWitness>,
25    PartialMmr,
26);
27
28impl StateView {
29    /// Returns data needed by the block producer to construct and prove the next block.
30    pub async fn get_block_inputs(
31        &self,
32        account_ids: Vec<AccountId>,
33        nullifiers: Vec<Nullifier>,
34        unauthenticated_note_commitments: BTreeSet<Word>,
35        reference_blocks: BTreeSet<BlockNumber>,
36    ) -> Result<BlockInputs, GetBlockInputsError> {
37        // Get the note inclusion proofs from the DB first: the reference blocks of the note proofs
38        // are needed below to fetch their authentication paths in the chain MMR. The proofs are
39        // scoped by the view's tip, so the database cannot report a note from a block the pinned
40        // snapshot cannot prove yet.
41        let unauthenticated_note_proofs = self
42            .db
43            .select_note_inclusion_proofs(unauthenticated_note_commitments, self.tip())
44            .await
45            .map_err(GetBlockInputsError::SelectNoteInclusionProofError)?;
46
47        // The set of blocks that the notes are included in.
48        let note_proof_reference_blocks =
49            unauthenticated_note_proofs.values().map(|proof| proof.location().block_num());
50
51        // Collect all blocks we need to prove inclusion for, without duplicates.
52        let mut blocks = reference_blocks;
53        blocks.extend(note_proof_reference_blocks);
54
55        let (latest, blocks, account_witnesses, nullifier_witnesses, partial_mmr) =
56            self.get_block_inputs_witnesses(blocks, &account_ids, &nullifiers)?;
57
58        // Fetch the block headers for all blocks in the partial MMR plus the latest one which will
59        // be used as the previous block header of the block being built.
60        let mut headers = self
61            .db
62            .select_block_headers(blocks.into_iter().chain(std::iter::once(latest)))
63            .await
64            .map_err(GetBlockInputsError::SelectBlockHeaderError)?;
65
66        // Find and remove the latest block as we must not add it to the chain MMR, since it is not
67        // yet in the chain.
68        let latest_block_header_index = headers
69            .iter()
70            .enumerate()
71            .find_map(|(index, header)| (header.block_num() == *latest).then_some(index))
72            .expect("DB should have returned the header of the latest block header");
73
74        // The order doesn't matter for PartialBlockchain::new, so swap remove is fine.
75        let latest_block_header = headers.swap_remove(latest_block_header_index);
76
77        // SAFETY: This should not error because:
78        // - we're passing exactly the block headers that we've added to the partial MMR,
79        // - so none of the block header's block numbers should exceed the chain length of the
80        //   partial MMR,
81        // - and we've added blocks to a BTreeSet, so there can be no duplicates.
82        //
83        // We construct headers and partial MMR in concert, so they are consistent. This is why we
84        // can call the unchecked constructor.
85        let partial_block_chain = PartialBlockchain::new_unchecked(partial_mmr, headers)
86            .expect("partial mmr and block headers should be consistent");
87
88        Ok(BlockInputs::new(
89            latest_block_header,
90            partial_block_chain,
91            account_witnesses,
92            nullifier_witnesses,
93            unauthenticated_note_proofs,
94        ))
95    }
96
97    /// Get account and nullifier witnesses for the requested account IDs and nullifiers, the
98    /// [`PartialMmr`] for the given blocks, and the blocks as scoped block numbers alongside the
99    /// scoped latest block. The MMR and the returned blocks won't contain the latest block.
100    fn get_block_inputs_witnesses(
101        &self,
102        mut blocks: BTreeSet<BlockNumber>,
103        account_ids: &[AccountId],
104        nullifiers: &[Nullifier],
105    ) -> Result<BlockInputWitnesses, GetBlockInputsError> {
106        self.with_inner_read_blocking(|inner| {
107            let latest_block_number = inner.latest_block_num();
108
109            // The latest block is not yet in the chain MMR, so we can't (and don't need to) prove
110            // its inclusion in the chain.
111            blocks.remove(&latest_block_number);
112
113            // Scoping the blocks doubles as the validation that none lies beyond the view's tip
114            // (which equals the latest block number of this pinned snapshot). Scoped in descending
115            // order, so the first failure carries the highest block number.
116            let scoped_blocks = blocks
117                .iter()
118                .rev()
119                .map(|&block| {
120                    self.scope_block(block).ok_or(
121                        GetBlockInputsError::UnknownBatchBlockReference {
122                            highest_block_number: block,
123                            latest_block_number,
124                        },
125                    )
126                })
127                .collect::<Result<Vec<_>, _>>()?;
128
129            // Fetch the partial MMR at the state of the latest block with authentication paths for
130            // the provided set of blocks.
131            //
132            // SAFETY:
133            // - The latest block num was retrieved from the inner blockchain from which we will
134            //   also retrieve the proofs, so it is guaranteed to exist in that chain.
135            // - Scoping above proved that no block in the set is greater than the latest block
136            //   number *and* the latest block num was removed from the set. Therefore only block
137            //   numbers smaller than latest block num remain in the set. Therefore all the block
138            //   numbers are guaranteed to exist in the chain state at latest block num.
139            let partial_mmr =
140                inner.blockchain.partial_mmr_from_blocks(&blocks, latest_block_number).expect(
141                    "latest block num should exist and all blocks in set should be < than latest block",
142                );
143
144            // Fetch witnesses for all accounts.
145            let account_witnesses = account_ids
146                .iter()
147                .copied()
148                .map(|account_id| (account_id, inner.account_tree.open_latest(account_id)))
149                .collect::<BTreeMap<AccountId, AccountWitness>>();
150
151            // Fetch witnesses for all nullifiers. We don't check whether the nullifiers are spent
152            // or not as this is done as part of proposing the block.
153            let nullifier_witnesses: BTreeMap<Nullifier, NullifierWitness> = nullifiers
154                .iter()
155                .copied()
156                .map(|nullifier| (nullifier, inner.nullifier_tree.open(&nullifier)))
157                .collect();
158
159            Ok((
160                self.tip(),
161                scoped_blocks,
162                account_witnesses,
163                nullifier_witnesses,
164                partial_mmr,
165            ))
166        })
167    }
168}