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}