Skip to main content

miden_protocol/block/
proposed_block.rs

1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::vec::Vec;
4
5use crate::account::{AccountId, AccountUpdateDetails};
6use crate::batch::note_tracker::{NoteTracker, TrackerOutput};
7use crate::batch::{BatchAccountUpdate, BatchId, OrderedBatches, ProvenBatch};
8use crate::block::account_tree::{AccountWitness, PartialAccountTree};
9use crate::block::block_inputs::BlockInputs;
10use crate::block::nullifier_tree::{NullifierWitness, PartialNullifierTree};
11use crate::block::{
12    AccountUpdateWitness,
13    BlockBody,
14    BlockHeader,
15    BlockNoteIndex,
16    BlockNoteTree,
17    BlockNumber,
18    OutputNoteBatch,
19    ValidatorConfig,
20};
21use crate::errors::ProposedBlockError;
22use crate::note::{NoteId, Nullifier};
23use crate::protocol_config::NextProtocolConfig;
24use crate::transaction::{InputNoteCommitment, OutputNote, PartialBlockchain, TransactionHeader};
25use crate::utils::serde::{
26    ByteReader,
27    ByteWriter,
28    Deserializable,
29    DeserializationError,
30    Serializable,
31};
32use crate::{EMPTY_WORD, MAX_BATCHES_PER_BLOCK, Word};
33
34// PROPOSED BLOCK
35// =================================================================================================
36
37/// A proposed block with many, but not all constraints of a
38/// [`ProvenBlock`](crate::block::ProvenBlock) enforced.
39///
40/// See [`ProposedBlock::new_at`] for details on the checks.
41#[derive(Debug, Clone)]
42pub struct ProposedBlock {
43    /// The transaction batches in this block.
44    batches: OrderedBatches,
45    /// The unix timestamp of the block in seconds.
46    timestamp: u32,
47    /// All account's [`AccountUpdateWitness`] that were updated in this block. See its docs for
48    /// details.
49    account_updated_witnesses: Vec<(AccountId, AccountUpdateWitness)>,
50    /// Note batches created by the transactions in this block.
51    ///
52    /// These are the output notes after note erasure has been done, so they represent the actual
53    /// output notes of the block.
54    ///
55    /// The length of this vector is guaranteed to be equal to the length of `batches` and the
56    /// inner batch of output notes may be empty if a batch did not create any notes.
57    output_note_batches: Vec<OutputNoteBatch>,
58    /// The nullifiers created by this block.
59    ///
60    /// These are the nullifiers of all input notes after note erasure has been done, so these are
61    /// the nullifiers of all _authenticated_ notes consumed in the block.
62    created_nullifiers: BTreeMap<Nullifier, NullifierWitness>,
63    /// The [`PartialBlockchain`] at the state of the previous block header. It is used to:
64    /// - authenticate unauthenticated notes whose note inclusion proof references a block.
65    /// - authenticate all reference blocks of the batches in this block.
66    partial_blockchain: PartialBlockchain,
67    /// The previous block's header which this block builds on top of.
68    ///
69    /// As part of proving the block, this header will be added to the next partial blockchain.
70    prev_block_header: BlockHeader,
71    /// The validator public key set authorized to sign the *next* block, which is committed to in
72    /// this block's header.
73    ///
74    /// Defaults to the previous block's `validator_config` (i.e. no rotation). Set a different set
75    /// via [`ProposedBlock::with_next_validator_config`] to rotate the validator keys.
76    next_validator_config: ValidatorConfig,
77    /// The protocol upgrade this block schedules, which is committed to in this block's header.
78    ///
79    /// Defaults to the previous block's `next_protocol_config` (i.e. no change). Set a different
80    /// value via [`ProposedBlock::with_next_protocol_config`] to schedule an upgrade.
81    next_protocol_config: Option<NextProtocolConfig>,
82}
83
84impl ProposedBlock {
85    // CONSTRUCTORS
86    // --------------------------------------------------------------------------------------------
87
88    /// Creates a new proposed block from the provided [`BlockInputs`], transaction batches and
89    /// timestamp.
90    ///
91    /// This checks most of the constraints of a block and computes most of the data structure
92    /// updates except for the more expensive tree updates (nullifier, account and chain
93    /// commitment).
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if any of the following conditions are met.
98    ///
99    /// ## Batches
100    ///
101    /// - The number of batches exceeds [`MAX_BATCHES_PER_BLOCK`].
102    /// - There are duplicate batches, i.e. they have the same [`BatchId`].
103    /// - The expiration block number of any batch is less than the block number of the currently
104    ///   proposed block.
105    ///
106    /// ## Chain
107    ///
108    /// - The length of the [`PartialBlockchain`] in the block inputs is not equal to the previous
109    ///   block header in the block inputs.
110    /// - The [`PartialBlockchain`]'s chain commitment is not equal to the
111    ///   [`BlockHeader::chain_commitment`] of the previous block header.
112    ///
113    /// ## Notes
114    ///
115    /// Note that, in the following, the set of authenticated notes includes unauthenticated notes
116    /// that have been authenticated.
117    ///
118    /// - The union of all input notes across all batches contain duplicates.
119    /// - The union of all output notes across all batches contain duplicates.
120    /// - An unauthenticated note is consumed before it is created (as determined by the order in
121    ///   which batches are given).
122    /// - There is a note inclusion proof for an unauthenticated note whose referenced block is not
123    ///   in the [`PartialBlockchain`].
124    /// - The note inclusion proof for an unauthenticated is invalid.
125    /// - There are any unauthenticated notes for which no note inclusion proof is provided.
126    /// - A [`NullifierWitness`] is missing for an authenticated note.
127    /// - If the [`NullifierWitness`] for an authenticated note proves that the note was already
128    ///   consumed.
129    ///
130    /// ## Accounts
131    ///
132    /// - An [`AccountWitness`] is missing for an account updated by a batch.
133    /// - Any two batches update the same account from the same state. For example, if batch 1
134    ///   updates some account from state A to B and batch 2 updates it from A to F, then those
135    ///   batches conflict as they both start from the same initial state but produce a fork in the
136    ///   account's state.
137    /// - Account updates from different batches cannot be brought in a contiguous order. For
138    ///   example, if a batch 1 updates an account from state A to C, and a batch 2 updates it from
139    ///   D to F, then the state transition from C to D is missing. Note that this does not mean,
140    ///   that batches must be provided in an order where account updates chain together in the
141    ///   order of the batches, which would generally be an impossible requirement to fulfill.
142    /// - Account updates cannot be merged, i.e. if [`AccountUpdateDetails::merge`] fails on the
143    ///   updates from two batches.
144    ///
145    /// ## Time
146    ///
147    /// - The given `timestamp` does not increase monotonically compared to the previous block
148    ///   header' timestamp.
149    pub fn new_at(
150        block_inputs: BlockInputs,
151        batches: Vec<ProvenBatch>,
152        timestamp: u32,
153    ) -> Result<Self, ProposedBlockError> {
154        // Check for duplicate and max number of batches.
155        // --------------------------------------------------------------------------------------------
156
157        if batches.len() > MAX_BATCHES_PER_BLOCK {
158            return Err(ProposedBlockError::TooManyBatches);
159        }
160
161        check_duplicate_batches(&batches)?;
162
163        // Check timestamp increases monotonically.
164        // --------------------------------------------------------------------------------------------
165
166        check_timestamp_increases_monotonically(timestamp, block_inputs.prev_block_header())?;
167
168        // Check for batch expiration.
169        // --------------------------------------------------------------------------------------------
170
171        check_batch_expiration(&batches, block_inputs.prev_block_header())?;
172
173        // Check for consistency between the partial blockchain and the referenced previous block.
174        // --------------------------------------------------------------------------------------------
175
176        check_reference_block_partial_blockchain_consistency(
177            block_inputs.partial_blockchain(),
178            block_inputs.prev_block_header(),
179        )?;
180
181        // Check every block referenced by a batch is in the partial blockchain.
182        // --------------------------------------------------------------------------------------------
183
184        check_batch_reference_blocks(
185            block_inputs.partial_blockchain(),
186            block_inputs.prev_block_header(),
187            &batches,
188        )?;
189
190        // Check for duplicates in the input and output notes and compute the input and output notes
191        // of the block by erasing notes that are created and consumed within this block as well as
192        // authenticating unauthenticated notes.
193        // --------------------------------------------------------------------------------------------
194
195        let mut tracker = NoteTracker::new(
196            block_inputs.partial_blockchain(),
197            block_inputs.prev_block_header(),
198            block_inputs.unauthenticated_note_proofs(),
199        );
200        for batch in batches.iter() {
201            tracker.push(batch)?;
202        }
203        let TrackerOutput {
204            input_notes: block_input_notes,
205            erased_notes: block_erased_notes,
206            output_notes: block_output_notes,
207        } = tracker.finalize()?;
208
209        // All unauthenticated notes must be erased or authenticated by now.
210        if let Some(nullifier) = block_input_notes
211            .iter()
212            .find_map(|note| (!note.is_authenticated()).then_some(note.nullifier()))
213        {
214            return Err(ProposedBlockError::UnauthenticatedNoteConsumed { nullifier });
215        }
216
217        // Check for nullifiers proofs and unspent nullifiers.
218        // --------------------------------------------------------------------------------------------
219
220        let (prev_block_header, partial_blockchain, account_witnesses, mut nullifier_witnesses, _) =
221            block_inputs.into_parts();
222
223        // Remove nullifiers of erased notes, so we only add the nullifiers of actual input notes to
224        // the proposed block.
225        remove_erased_nullifiers(&mut nullifier_witnesses, block_erased_notes.into_iter());
226
227        // Check against computed block_input_notes which also contain unauthenticated notes that
228        // have been authenticated.
229        check_nullifiers(
230            &nullifier_witnesses,
231            block_input_notes.iter().map(InputNoteCommitment::nullifier),
232        )?;
233
234        // Aggregate account updates across batches.
235        // --------------------------------------------------------------------------------------------
236
237        let aggregator = AccountUpdateAggregator::from_batches(&batches)?;
238        let account_updated_witnesses = aggregator.into_update_witnesses(account_witnesses)?;
239
240        // Compute the block's output note batches from the individual batch output notes.
241        // --------------------------------------------------------------------------------------------
242
243        let output_note_batches = compute_block_output_notes(&batches, block_output_notes);
244
245        // Build proposed blocks from parts.
246        // --------------------------------------------------------------------------------------------
247
248        let next_validator_config = prev_block_header.validator_config().clone();
249        let next_protocol_config = prev_block_header.next_protocol_config().cloned();
250
251        Ok(Self {
252            batches: OrderedBatches::new(batches),
253            timestamp,
254            account_updated_witnesses,
255            output_note_batches,
256            created_nullifiers: nullifier_witnesses,
257            partial_blockchain,
258            prev_block_header,
259            next_validator_config,
260            next_protocol_config,
261        })
262    }
263
264    /// Creates a new proposed block from the provided [`BlockInputs`] and transaction batches.
265    ///
266    /// Equivalent to [`ProposedBlock::new_at`] except that the timestamp of the proposed block is
267    /// set to the current system time or the previous block header's timestamp + 1, whichever
268    /// is greater. This guarantees that the timestamp increases monotonically.
269    ///
270    /// See the [`ProposedBlock::new_at`] for details on errors and other constraints.
271    #[cfg(feature = "std")]
272    pub fn new(
273        block_inputs: BlockInputs,
274        batches: Vec<ProvenBatch>,
275    ) -> Result<Self, ProposedBlockError> {
276        let timestamp_now: u32 = std::time::SystemTime::now()
277            .duration_since(std::time::UNIX_EPOCH)
278            .expect("now should be after 1970")
279            .as_secs()
280            .try_into()
281            .expect("timestamp should fit in a u32 before the year 2106");
282
283        let timestamp = timestamp_now.max(block_inputs.prev_block_header().timestamp() + 1);
284
285        Self::new_at(block_inputs, batches, timestamp)
286    }
287
288    // BUILDERS
289    // --------------------------------------------------------------------------------------------
290
291    /// Sets the validator key set that this block commits to as the signer of the *next* block,
292    /// rotating away from the previous block's validator keys.
293    ///
294    /// The block this proposed block produces is still signed by the current validators (the keys
295    /// committed to by the previous block); the provided set only takes effect for the following
296    /// block.
297    #[must_use]
298    pub fn with_next_validator_config(mut self, next_validator_config: ValidatorConfig) -> Self {
299        self.next_validator_config = next_validator_config;
300        self
301    }
302
303    /// Sets the protocol upgrade that this block schedules.
304    #[must_use]
305    pub fn with_next_protocol_config(
306        mut self,
307        next_protocol_config: Option<NextProtocolConfig>,
308    ) -> Self {
309        self.next_protocol_config = next_protocol_config;
310        self
311    }
312
313    // ACCESSORS
314    // --------------------------------------------------------------------------------------------
315
316    /// Returns the block number of this proposed block.
317    pub fn block_num(&self) -> BlockNumber {
318        // The chain length is the length at the state of the previous block header, so we have to
319        // add one.
320        self.partial_blockchain().chain_length() + 1
321    }
322
323    /// Returns a reference to the previous block header that this block builds on top of.
324    pub fn prev_block_header(&self) -> &BlockHeader {
325        &self.prev_block_header
326    }
327
328    /// Returns the [`PartialBlockchain`] that this block contains.
329    pub fn partial_blockchain(&self) -> &PartialBlockchain {
330        &self.partial_blockchain
331    }
332
333    /// Returns a reference to the slice of transaction batches in this block.
334    pub fn batches(&self) -> &OrderedBatches {
335        &self.batches
336    }
337
338    /// Returns an iterator over all transactions in the block.
339    pub fn transactions(&self) -> impl Iterator<Item = &TransactionHeader> {
340        self.batches
341            .as_slice()
342            .iter()
343            .flat_map(|batch| batch.transactions().as_slice().iter())
344    }
345
346    /// Returns the map of nullifiers to their proofs from the proposed block.
347    pub fn created_nullifiers(&self) -> &BTreeMap<Nullifier, NullifierWitness> {
348        &self.created_nullifiers
349    }
350
351    /// Returns a reference to the slice of accounts updated in this block.
352    pub fn updated_accounts(&self) -> &[(AccountId, AccountUpdateWitness)] {
353        &self.account_updated_witnesses
354    }
355
356    /// Returns a slice of the [`OutputNoteBatch`] of each batch in this block.
357    pub fn output_note_batches(&self) -> &[OutputNoteBatch] {
358        &self.output_note_batches
359    }
360
361    /// Returns the timestamp of this block.
362    pub fn timestamp(&self) -> u32 {
363        self.timestamp
364    }
365
366    /// Returns the validator key set committed to by this block as the signer of the next block.
367    pub fn next_validator_config(&self) -> &ValidatorConfig {
368        &self.next_validator_config
369    }
370
371    /// Returns the protocol upgrade scheduled by this block, if any.
372    pub fn next_protocol_config(&self) -> Option<&NextProtocolConfig> {
373        self.next_protocol_config.as_ref()
374    }
375
376    // COMMITMENT COMPUTATIONS
377    // --------------------------------------------------------------------------------------------
378
379    /// Computes the new account tree root after the given updates.
380    pub fn compute_account_root(&self) -> Result<Word, ProposedBlockError> {
381        // If no accounts were updated, the account tree root is unchanged.
382        if self.account_updated_witnesses.is_empty() {
383            return Ok(self.prev_block_header.account_root());
384        }
385
386        // First reconstruct the current account tree from the provided merkle paths.
387        // If a witness points to a leaf where multiple account IDs share the same prefix, this will
388        // return an error.
389        let mut partial_account_tree = PartialAccountTree::with_witnesses(
390            self.account_updated_witnesses
391                .iter()
392                .map(|(_, update_witness)| update_witness.to_witness()),
393        )
394        .map_err(|source| ProposedBlockError::AccountWitnessTracking { source })?;
395
396        // Check the account tree root in the previous block header matches the reconstructed tree's
397        // root.
398        if self.prev_block_header.account_root() != partial_account_tree.root() {
399            return Err(ProposedBlockError::StaleAccountTreeRoot {
400                prev_block_account_root: self.prev_block_header.account_root(),
401                stale_account_root: partial_account_tree.root(),
402            });
403        }
404
405        // Second, update the account tree by inserting the new final account state commitments to
406        // compute the new root of the account tree.
407        // If an account ID's prefix already exists in the tree, this will return an error.
408        // Note that we have inserted all witnesses that we want to update into the partial account
409        // tree, so we should not run into the untracked key error.
410        partial_account_tree
411            .upsert_state_commitments(self.account_updated_witnesses.iter().map(
412                |(account_id, update_witness)| {
413                    (*account_id, update_witness.final_state_commitment())
414                },
415            ))
416            .map_err(|source| ProposedBlockError::AccountIdPrefixDuplicate { source })?;
417
418        Ok(partial_account_tree.root())
419    }
420
421    /// Computes the new nullifier root by inserting the nullifier witnesses into a partial
422    /// nullifier tree and marking each nullifier as spent in the given block number.
423    pub fn compute_nullifier_root(&self) -> Result<Word, ProposedBlockError> {
424        // If no nullifiers were created, the nullifier tree root is unchanged.
425        if self.created_nullifiers.is_empty() {
426            return Ok(self.prev_block_header.nullifier_root());
427        }
428
429        // First, reconstruct the current nullifier tree with the merkle paths of the nullifiers we
430        // want to update.
431        // Due to the guarantees of ProposedBlock we can safely assume that each nullifier is mapped
432        // to its corresponding nullifier witness, so we don't have to check again whether
433        // they match.
434        let mut partial_nullifier_tree =
435            PartialNullifierTree::with_witnesses(self.created_nullifiers().values().cloned())
436                .map_err(ProposedBlockError::NullifierWitnessRootMismatch)?;
437
438        // Check the nullifier tree root in the previous block header matches the reconstructed
439        // tree's root.
440        if self.prev_block_header.nullifier_root() != partial_nullifier_tree.root() {
441            return Err(ProposedBlockError::StaleNullifierTreeRoot {
442                prev_block_nullifier_root: self.prev_block_header.nullifier_root(),
443                stale_nullifier_root: partial_nullifier_tree.root(),
444            });
445        }
446
447        // Second, mark each nullifier as spent in the tree. Note that checking whether each
448        // nullifier is unspent is checked as part of constructing the proposed block.
449
450        // SAFETY: As mentioned above, we can safely assume that each nullifier's witness was
451        // added and every nullifier should be tracked by the partial tree and
452        // therefore updatable.
453        partial_nullifier_tree
454            .mark_spent_all(self.created_nullifiers.keys().copied(), self.block_num())
455            .expect("nullifiers' merkle path should have been added to the partial tree and the nullifiers should be unspent");
456
457        Ok(partial_nullifier_tree.root())
458    }
459
460    /// Compute the block note tree from the output note batches.
461    pub fn compute_block_note_tree(&self) -> BlockNoteTree {
462        let output_notes_iter =
463            self.output_note_batches.iter().enumerate().flat_map(|(batch_idx, notes)| {
464                notes.iter().map(move |(note_idx_in_batch, note)| {
465                    (
466                        // SAFETY: The proposed block contains at most the max allowed number of
467                        // batches and each batch is guaranteed to contain at most
468                        // the max allowed number of output notes.
469                        BlockNoteIndex::new(batch_idx, *note_idx_in_batch).expect(
470                            "max batches in block and max notes in batches should be enforced",
471                        ),
472                        note.into(),
473                    )
474                })
475            });
476
477        // SAFETY: We only construct proposed blocks that:
478        // - do not contain duplicates
479        // - contain at most the max allowed number of batches and each batch is guaranteed to
480        //   contain at most the max allowed number of output notes.
481        BlockNoteTree::with_entries(output_notes_iter)
482            .expect("the output notes of the block should not contain duplicates and contain at most the allowed maximum")
483    }
484
485    /// Adds the commitment of the previous block header to the partial blockchain to compute the
486    /// new chain commitment.
487    pub fn compute_chain_commitment(&self) -> Word {
488        let mut partial_blockchain = self.partial_blockchain.clone();
489        // SAFETY: This does not panic as long as the block header we're adding is the next one in
490        // the chain which is validated as part of constructing a `ProposedBlock`.
491        partial_blockchain.add_block(&self.prev_block_header, true);
492        partial_blockchain.peaks().hash_peaks()
493    }
494
495    // STATE MUTATORS
496    // --------------------------------------------------------------------------------------------
497
498    /// Builds a [`BlockHeader`] and [`BlockBody`] by computing the following from the state
499    /// updates encapsulated by the provided [`ProposedBlock`]:
500    /// - the account root;
501    /// - the nullifier root;
502    /// - the note root;
503    /// - the transaction commitment; and
504    /// - the chain commitment.
505    ///
506    /// # Errors
507    ///
508    /// Returns an error if any of the following conditions are met.
509    ///
510    /// ## Account Tree
511    ///
512    /// - An account witness cannot be used to reconstruct the partial account tree (e.g. it points
513    ///   to a leaf where multiple account IDs share the same prefix).
514    /// - The account root in the previous block header does not match the root of the reconstructed
515    ///   partial account tree (stale account tree root).
516    /// - An account ID's prefix already exists in the tree when inserting the new state commitments
517    ///   (duplicate account ID prefix).
518    ///
519    /// ## Nullifier Tree
520    ///
521    /// - The nullifier witnesses cannot be used to reconstruct the partial nullifier tree (root
522    ///   mismatch between witnesses).
523    /// - The nullifier root in the previous block header does not match the root of the
524    ///   reconstructed partial nullifier tree (stale nullifier tree root).
525    pub fn into_header_and_body(self) -> Result<(BlockHeader, BlockBody), ProposedBlockError> {
526        // Get fields from the proposed block before it is consumed.
527        let block_num = self.block_num();
528        let timestamp = self.timestamp();
529        let prev_block_header = self.prev_block_header().clone();
530        let next_validator_config = self.next_validator_config.clone();
531        let next_protocol_config = self.next_protocol_config.clone();
532
533        // Insert the state commitments of updated accounts into the account tree to compute its new
534        // root.
535        let new_account_root = self.compute_account_root()?;
536
537        // Insert the created nullifiers into the nullifier tree to compute its new root.
538        let new_nullifier_root = self.compute_nullifier_root()?;
539
540        // Compute the root of the block note tree.
541        let note_tree = self.compute_block_note_tree();
542        let note_root = note_tree.root();
543
544        // Insert the previous block header into the block partial blockchain to get the new chain
545        // commitment.
546        // TODO: Consider avoiding the partial blockchain clone by constructing `BlockBody` from its
547        // raw parts, which does not require the partial blockchain.
548        let new_chain_commitment = self.compute_chain_commitment();
549
550        // Construct the block body from the proposed block.
551        let body = BlockBody::from(self);
552
553        // Construct the header.
554        let tx_commitment = body.transaction_commitment();
555        let prev_block_commitment = prev_block_header.commitment();
556
557        // For now we copy the parameters of the previous header, which means the parameters set on
558        // the genesis block will be passed through. Eventually, the contained base fees will be
559        // updated based on the demand in the currently proposed block.
560        let fee_parameters = prev_block_header.fee_parameters().clone();
561        let protocol_config_commitment = prev_block_header.protocol_config_commitment();
562
563        let header = BlockHeader::new(
564            prev_block_commitment,
565            block_num,
566            new_chain_commitment,
567            new_account_root,
568            new_nullifier_root,
569            note_root,
570            tx_commitment,
571            next_validator_config,
572            fee_parameters,
573            protocol_config_commitment,
574            next_protocol_config,
575            timestamp,
576        );
577
578        Ok((header, body))
579    }
580
581    /// Consumes self and returns the non-[`Copy`] parts of the block.
582    #[allow(clippy::type_complexity)]
583    pub fn into_parts(
584        self,
585    ) -> (
586        OrderedBatches,
587        Vec<(AccountId, AccountUpdateWitness)>,
588        Vec<OutputNoteBatch>,
589        BTreeMap<Nullifier, NullifierWitness>,
590        PartialBlockchain,
591        BlockHeader,
592    ) {
593        (
594            self.batches,
595            self.account_updated_witnesses,
596            self.output_note_batches,
597            self.created_nullifiers,
598            self.partial_blockchain,
599            self.prev_block_header,
600        )
601    }
602}
603
604// SERIALIZATION
605// ================================================================================================
606
607impl Serializable for ProposedBlock {
608    fn write_into<W: ByteWriter>(&self, target: &mut W) {
609        self.batches.write_into(target);
610        self.timestamp.write_into(target);
611        self.account_updated_witnesses.write_into(target);
612        self.output_note_batches.write_into(target);
613        self.created_nullifiers.write_into(target);
614        self.partial_blockchain.write_into(target);
615        self.prev_block_header.write_into(target);
616        self.next_validator_config.write_into(target);
617        self.next_protocol_config.write_into(target);
618    }
619}
620
621impl Deserializable for ProposedBlock {
622    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
623        let block = Self {
624            batches: OrderedBatches::read_from(source)?,
625            timestamp: u32::read_from(source)?,
626            account_updated_witnesses: <Vec<(AccountId, AccountUpdateWitness)>>::read_from(source)?,
627            output_note_batches: <Vec<OutputNoteBatch>>::read_from(source)?,
628            created_nullifiers: <BTreeMap<Nullifier, NullifierWitness>>::read_from(source)?,
629            partial_blockchain: PartialBlockchain::read_from(source)?,
630            prev_block_header: BlockHeader::read_from(source)?,
631            next_validator_config: ValidatorConfig::read_from(source)?,
632            next_protocol_config: <Option<NextProtocolConfig>>::read_from(source)?,
633        };
634
635        Ok(block)
636    }
637}
638
639// HELPER FUNCTIONS
640// ================================================================================================
641
642fn check_duplicate_batches(batches: &[ProvenBatch]) -> Result<(), ProposedBlockError> {
643    let mut input_note_set = BTreeSet::new();
644
645    for batch in batches {
646        if !input_note_set.insert(batch.id()) {
647            return Err(ProposedBlockError::DuplicateBatch { batch_id: batch.id() });
648        }
649    }
650
651    Ok(())
652}
653
654fn check_timestamp_increases_monotonically(
655    provided_timestamp: u32,
656    prev_block_header: &BlockHeader,
657) -> Result<(), ProposedBlockError> {
658    if provided_timestamp <= prev_block_header.timestamp() {
659        Err(ProposedBlockError::TimestampDoesNotIncreaseMonotonically {
660            provided_timestamp,
661            previous_timestamp: prev_block_header.timestamp(),
662        })
663    } else {
664        Ok(())
665    }
666}
667
668/// Checks whether any of the batches is expired and can no longer be included in this block.
669///
670/// To illustrate, a batch which expired at block 4 cannot be included in block 5, but if it
671/// expires at block 5 then it can still be included in block 5.
672fn check_batch_expiration(
673    batches: &[ProvenBatch],
674    prev_block_header: &BlockHeader,
675) -> Result<(), ProposedBlockError> {
676    let current_block_num = prev_block_header.block_num() + 1;
677
678    for batch in batches {
679        if batch.batch_expiration_block_num() < current_block_num {
680            return Err(ProposedBlockError::ExpiredBatch {
681                batch_id: batch.id(),
682                batch_expiration_block_num: batch.batch_expiration_block_num(),
683                current_block_num,
684            });
685        }
686    }
687
688    Ok(())
689}
690
691/// Check that each nullifier in the block has a proof provided and that the nullifier is
692/// unspent. The proofs are required to update the nullifier tree.
693fn check_nullifiers(
694    nullifier_witnesses: &BTreeMap<Nullifier, NullifierWitness>,
695    block_input_notes: impl Iterator<Item = Nullifier>,
696) -> Result<(), ProposedBlockError> {
697    for block_input_note in block_input_notes {
698        match nullifier_witnesses
699            .get(&block_input_note)
700            .and_then(|x| x.proof().get(&block_input_note.as_word()))
701        {
702            Some(nullifier_value) => {
703                if nullifier_value != EMPTY_WORD {
704                    return Err(ProposedBlockError::NullifierSpent(block_input_note));
705                }
706            },
707            // If the nullifier witnesses did not contain a proof for this nullifier or the provided
708            // proof was not for this nullifier, then it's an error.
709            None => return Err(ProposedBlockError::NullifierProofMissing(block_input_note)),
710        }
711    }
712
713    Ok(())
714}
715
716/// Removes the nullifiers from the nullifier witnesses that were erased (i.e. created and consumed
717/// within the block).
718fn remove_erased_nullifiers(
719    nullifier_witnesses: &mut BTreeMap<Nullifier, NullifierWitness>,
720    block_erased_notes: impl Iterator<Item = Nullifier>,
721) {
722    for erased_note in block_erased_notes {
723        // We do not check that the nullifier was actually present to allow the block inputs to
724        // not include a nullifier that is known to belong to an erased note.
725        let _ = nullifier_witnesses.remove(&erased_note);
726    }
727}
728
729/// Checks consistency between the previous block header and the provided partial blockchain.
730///
731/// This checks that:
732/// - the chain length of the partial blockchain is equal to the block number of the previous block
733///   header, i.e. the partial blockchain's latest block is the previous' blocks reference block.
734///   The previous block header will be added to the partial blockchain as part of constructing the
735///   current block.
736/// - the root of the partial blockchain is equivalent to the chain commitment of the previous block
737///   header.
738fn check_reference_block_partial_blockchain_consistency(
739    partial_blockchain: &PartialBlockchain,
740    prev_block_header: &BlockHeader,
741) -> Result<(), ProposedBlockError> {
742    // Make sure that the current partial blockchain has blocks up to prev_block_header - 1, i.e.
743    // its chain length is equal to the block number of the previous block header.
744    if partial_blockchain.chain_length() != prev_block_header.block_num() {
745        return Err(ProposedBlockError::ChainLengthNotEqualToPreviousBlockNumber {
746            chain_length: partial_blockchain.chain_length(),
747            prev_block_num: prev_block_header.block_num(),
748        });
749    }
750
751    let chain_commitment = partial_blockchain.peaks().hash_peaks();
752    if chain_commitment != prev_block_header.chain_commitment() {
753        return Err(ProposedBlockError::ChainRootNotEqualToPreviousBlockChainCommitment {
754            chain_commitment,
755            prev_block_chain_commitment: prev_block_header.chain_commitment(),
756            prev_block_num: prev_block_header.block_num(),
757        });
758    }
759
760    Ok(())
761}
762
763/// Check that each block referenced by a batch in the block has an entry in the partial blockchain,
764/// except if the referenced block is the same as the previous block, referenced by the block.
765fn check_batch_reference_blocks(
766    partial_blockchain: &PartialBlockchain,
767    prev_block_header: &BlockHeader,
768    batches: &[ProvenBatch],
769) -> Result<(), ProposedBlockError> {
770    for batch in batches {
771        let batch_reference_block_num = batch.reference_block_num();
772        if batch_reference_block_num != prev_block_header.block_num()
773            && !partial_blockchain.contains_block(batch.reference_block_num())
774        {
775            return Err(ProposedBlockError::BatchReferenceBlockMissingFromChain {
776                reference_block_num: batch.reference_block_num(),
777                batch_id: batch.id(),
778            });
779        }
780    }
781
782    Ok(())
783}
784
785/// Computes the block's output notes from the batches of notes of each batch in the block.
786///
787/// We pass in `block_output_notes` which is the full set of output notes of the block, with output
788/// notes erased that are consumed by some batch in the block.
789///
790/// The batch output notes of each proven batch however contain all the notes that it creates,
791/// including ones that were potentially erased in `block_output_notes`. This means we have to
792/// make the batch output notes consistent with `block_output_notes` by removing the erased notes.
793/// Then it accurately represents what output notes the batch actually creates as part of the block.
794///
795/// Returns the set of [`OutputNoteBatch`]es that each batch creates.
796fn compute_block_output_notes(
797    batches: &[ProvenBatch],
798    mut block_output_notes: BTreeMap<NoteId, (BatchId, OutputNote)>,
799) -> Vec<OutputNoteBatch> {
800    let mut block_output_note_batches = Vec::with_capacity(batches.len());
801
802    for batch in batches.iter() {
803        let batch_output_notes = compute_batch_output_notes(batch, &mut block_output_notes);
804        block_output_note_batches.push(batch_output_notes);
805    }
806
807    block_output_note_batches
808}
809
810/// Computes the output note of the given batch. This is essentially the batch's output notes minus
811/// all erased notes.
812///
813/// If a note in the batch's output notes is not present in the block output notes map it means it
814/// was erased and should therefore not be added to the batch's output notes. If it is present, it
815/// is added to the set of output notes of this batch.
816///
817/// The output note set is returned.
818fn compute_batch_output_notes(
819    batch: &ProvenBatch,
820    block_output_notes: &mut BTreeMap<NoteId, (BatchId, OutputNote)>,
821) -> OutputNoteBatch {
822    // The len of the batch output notes is an upper bound of how many notes the batch could've
823    // produced so we reserve that much space to avoid reallocation.
824    let mut batch_output_notes = Vec::with_capacity(batch.output_notes().len());
825
826    for (note_idx, original_output_note) in batch.output_notes().iter().enumerate() {
827        // If block_output_notes no longer contains a note it means it was erased and we do not
828        // include it in the output notes of the current batch. We include the original index of the
829        // note in the batch so we can later correctly construct the block note tree. This index is
830        // needed because we want to be able to construct the block note tree in two ways: 1) By
831        // inserting the individual batch note trees (with erased notes removed) as subtrees into an
832        // empty block note tree or 2) by iterating the set `OutputNoteBatch`es. If we did not store
833        // the index, then the second method would assume a contiguous layout of output notes and
834        // result in a different tree than the first method.
835        //
836        // Note that because we disallow duplicate output notes, if this map contains the
837        // original note id, then we can be certain it was created by this batch and should stay
838        // in the tree. In other words, there is no ambiguity where a note originated from.
839        if let Some((_batch_id, output_note)) =
840            block_output_notes.remove(&original_output_note.id())
841        {
842            debug_assert_eq!(
843                _batch_id,
844                batch.id(),
845                "batch that contained the note originally is no longer the batch that contains it according to the provided map"
846            );
847            batch_output_notes.push((note_idx, output_note));
848        }
849    }
850
851    batch_output_notes
852}
853
854// ACCOUNT UPDATE AGGREGATOR
855// ================================================================================================
856
857struct AccountUpdateAggregator {
858    /// The map from each account to the map of each of its updates, where the digest is the state
859    /// commitment from which the contained update starts.
860    /// An invariant of this field is that if the outer map has an entry for some account, the
861    /// inner update map is guaranteed to not be empty as well.
862    updates: BTreeMap<AccountId, BTreeMap<Word, (BatchAccountUpdate, BatchId)>>,
863}
864
865impl AccountUpdateAggregator {
866    fn new() -> Self {
867        Self { updates: BTreeMap::new() }
868    }
869
870    /// Aggregates all updates for the same account and stores each update indexed by its initial
871    /// state commitment so we can easily retrieve them in the next step. This lets us
872    /// chronologically order the updates per account across batches.
873    fn from_batches(batches: &[ProvenBatch]) -> Result<Self, ProposedBlockError> {
874        let mut update_aggregator = AccountUpdateAggregator::new();
875
876        for batch in batches {
877            for (account_id, update) in batch.account_updates() {
878                update_aggregator.insert_update(*account_id, batch.id(), update.clone())?;
879            }
880        }
881
882        Ok(update_aggregator)
883    }
884
885    /// Inserts the update from one batch for a specific account into the map of updates.
886    fn insert_update(
887        &mut self,
888        account_id: AccountId,
889        batch_id: BatchId,
890        update: BatchAccountUpdate,
891    ) -> Result<(), ProposedBlockError> {
892        // As a special case, a NOOP transaction (i.e. one where the initial and final state
893        // commitment is the same) can just be ignored without changing the outcome.
894        // Without this early return, such a transaction would conflict with other state-updating
895        // transactions, because there would be two transactions that update the account from
896        // the same initial state commitment.
897        if update.initial_state_commitment() == update.final_state_commitment() {
898            return Ok(());
899        };
900
901        if let Some((conflicting_update, conflicting_batch_id)) = self
902            .updates
903            .entry(account_id)
904            .or_default()
905            .insert(update.initial_state_commitment(), (update, batch_id))
906        {
907            return Err(ProposedBlockError::ConflictingBatchesUpdateSameAccount {
908                account_id,
909                initial_state_commitment: conflicting_update.initial_state_commitment(),
910                first_batch_id: conflicting_batch_id,
911                second_batch_id: batch_id,
912            });
913        }
914
915        Ok(())
916    }
917
918    /// Consumes self and aggregates the account updates from all contained accounts.
919    /// For each updated account an entry in `account_witnesses` must be present.
920    fn into_update_witnesses(
921        self,
922        mut account_witnesses: BTreeMap<AccountId, AccountWitness>,
923    ) -> Result<Vec<(AccountId, AccountUpdateWitness)>, ProposedBlockError> {
924        let mut account_update_witnesses = Vec::with_capacity(self.updates.len());
925
926        for (account_id, updates_map) in self.updates {
927            let witness = account_witnesses
928                .remove(&account_id)
929                .ok_or(ProposedBlockError::MissingAccountWitness(account_id))?;
930
931            let account_update_witness = Self::aggregate_account(account_id, witness, updates_map)?;
932
933            account_update_witnesses.push((account_id, account_update_witness));
934        }
935
936        Ok(account_update_witnesses)
937    }
938
939    /// Build the update for a single account from the provided map of updates, where each entry is
940    /// the state from which the update starts. This chains updates for this account together in a
941    /// chronological order using the state commitments to link them.
942    fn aggregate_account(
943        account_id: AccountId,
944        initial_state_proof: AccountWitness,
945        mut updates: BTreeMap<Word, (BatchAccountUpdate, BatchId)>,
946    ) -> Result<AccountUpdateWitness, ProposedBlockError> {
947        // The account witness could prove inclusion of a different ID in which case the initial
948        // state commitment of the current ID is the empty word.
949        let initial_state_commitment = if account_id == initial_state_proof.id() {
950            initial_state_proof.state_commitment()
951        } else {
952            Word::empty()
953        };
954
955        let mut details: Option<AccountUpdateDetails> = None;
956
957        let mut current_commitment = initial_state_commitment;
958        while !updates.is_empty() {
959            let (update, _) = updates.remove(&current_commitment).ok_or_else(|| {
960                ProposedBlockError::InconsistentAccountStateTransition {
961                    account_id,
962                    state_commitment: current_commitment,
963                    remaining_state_commitments: updates.keys().copied().collect(),
964                }
965            })?;
966
967            current_commitment = update.final_state_commitment();
968            let update_details = update.into_update();
969
970            details = Some(match details {
971                None => update_details,
972                Some(details) => details.merge(update_details).map_err(|source| {
973                    ProposedBlockError::AccountUpdateError { account_id, source: Box::new(source) }
974                })?,
975            });
976        }
977
978        Ok(AccountUpdateWitness::new(
979            initial_state_commitment,
980            current_commitment,
981            initial_state_proof,
982            details.expect("details should be Some as updates is guaranteed to not be empty"),
983        ))
984    }
985}