miden_objects/block/
mod.rs

1use alloc::{collections::BTreeSet, string::ToString, vec::Vec};
2
3use super::{
4    Digest, Felt, Hasher, MAX_ACCOUNTS_PER_BLOCK, MAX_BATCHES_PER_BLOCK, MAX_INPUT_NOTES_PER_BLOCK,
5    MAX_OUTPUT_NOTES_PER_BATCH, MAX_OUTPUT_NOTES_PER_BLOCK, ZERO,
6};
7
8mod header;
9pub use header::BlockHeader;
10mod block_number;
11pub use block_number::BlockNumber;
12mod note_tree;
13pub use note_tree::{BlockNoteIndex, BlockNoteTree};
14
15use crate::{
16    account::{delta::AccountUpdateDetails, AccountId},
17    errors::BlockError,
18    note::Nullifier,
19    transaction::{OutputNote, TransactionId},
20    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
21};
22
23pub type NoteBatch = Vec<OutputNote>;
24
25// BLOCK
26// ================================================================================================
27
28/// A block in the Miden chain.
29///
30/// A block contains information resulting from executing a set of transactions against the chain
31/// state defined by the previous block. It consists of 3 main components:
32/// - A set of change descriptors for all accounts updated in this block. For private accounts, the
33///   block contains only the new account state hashes; for public accounts, the block also contains
34///   a set of state deltas which can be applied to the previous account state to get the new
35///   account state.
36/// - A set of new notes created in this block. For private notes, the block contains only note IDs
37///   and note metadata; for public notes, full note details are recorded.
38/// - A set of new nullifiers created for all notes that were consumed in the block.
39///
40/// In addition to the above components, a block also contains a block header which contains
41/// commitments to the new state of the chain as well as a ZK proof attesting that a set of valid
42/// transactions was executed to transition the chain into the state described by this block (the
43/// ZK proof part is not yet implemented).
44#[derive(Debug, Clone)]
45pub struct Block {
46    /// Block header.
47    header: BlockHeader,
48
49    /// Account updates for the block.
50    updated_accounts: Vec<BlockAccountUpdate>,
51
52    /// Note batches created by the transactions in this block.
53    output_note_batches: Vec<NoteBatch>,
54
55    /// Nullifiers produced by the transactions in this block.
56    nullifiers: Vec<Nullifier>,
57    //
58    // TODO: add zk proof
59}
60
61impl Block {
62    /// Returns a new [Block] instantiated from the provided components.
63    ///
64    /// # Errors
65    /// Returns an error if block didn't pass validation.
66    ///
67    /// Note: consistency of the provided components is not validated.
68    pub fn new(
69        header: BlockHeader,
70        updated_accounts: Vec<BlockAccountUpdate>,
71        output_note_batches: Vec<NoteBatch>,
72        nullifiers: Vec<Nullifier>,
73    ) -> Result<Self, BlockError> {
74        let block = Self {
75            header,
76            updated_accounts,
77            output_note_batches,
78            nullifiers,
79        };
80
81        block.validate()?;
82
83        Ok(block)
84    }
85
86    /// Returns a commitment to this block.
87    pub fn hash(&self) -> Digest {
88        self.header.hash()
89    }
90
91    /// Returns the header of this block.
92    pub fn header(&self) -> BlockHeader {
93        self.header
94    }
95
96    /// Returns a set of account update descriptions for all accounts updated in this block.
97    pub fn updated_accounts(&self) -> &[BlockAccountUpdate] {
98        &self.updated_accounts
99    }
100
101    /// Returns a set of note batches containing all notes created in this block.
102    pub fn output_note_batches(&self) -> &[NoteBatch] {
103        &self.output_note_batches
104    }
105
106    /// Returns an iterator over all notes created in this block.
107    ///
108    /// Each note is accompanied by a corresponding index specifying where the note is located
109    /// in the block's note tree.
110    pub fn notes(&self) -> impl Iterator<Item = (BlockNoteIndex, &OutputNote)> {
111        self.output_note_batches.iter().enumerate().flat_map(|(batch_idx, notes)| {
112            notes.iter().enumerate().map(move |(note_idx_in_batch, note)| {
113                (
114                    BlockNoteIndex::new(batch_idx, note_idx_in_batch).expect(
115                        "Something went wrong: block is invalid, but passed or skipped validation",
116                    ),
117                    note,
118                )
119            })
120        })
121    }
122
123    /// Returns a note tree containing all notes created in this block.
124    pub fn build_note_tree(&self) -> BlockNoteTree {
125        let entries =
126            self.notes().map(|(note_index, note)| (note_index, note.id(), *note.metadata()));
127
128        BlockNoteTree::with_entries(entries)
129            .expect("Something went wrong: block is invalid, but passed or skipped validation")
130    }
131
132    /// Returns a set of nullifiers for all notes consumed in the block.
133    pub fn nullifiers(&self) -> &[Nullifier] {
134        &self.nullifiers
135    }
136
137    /// Returns an iterator over all transactions which affected accounts in the block with
138    /// corresponding account IDs.
139    pub fn transactions(&self) -> impl Iterator<Item = (TransactionId, AccountId)> + '_ {
140        self.updated_accounts.iter().flat_map(|update| {
141            update
142                .transactions
143                .iter()
144                .map(|transaction_id| (*transaction_id, update.account_id))
145        })
146    }
147
148    /// Computes a commitment to the transactions included in this block.
149    pub fn compute_tx_hash(&self) -> Digest {
150        compute_tx_hash(self.transactions())
151    }
152
153    // HELPER METHODS
154    // --------------------------------------------------------------------------------------------
155
156    fn validate(&self) -> Result<(), BlockError> {
157        let account_count = self.updated_accounts.len();
158        if account_count > MAX_ACCOUNTS_PER_BLOCK {
159            return Err(BlockError::TooManyAccountUpdates(account_count));
160        }
161
162        let batch_count = self.output_note_batches.len();
163        if batch_count > MAX_BATCHES_PER_BLOCK {
164            return Err(BlockError::TooManyTransactionBatches(batch_count));
165        }
166
167        // We can't check input notes here because they're not stored in the block,
168        // so we check that nullifier count is not bigger than maximum input notes per block.
169        let nullifier_count = self.nullifiers.len();
170        if nullifier_count > MAX_INPUT_NOTES_PER_BLOCK {
171            return Err(BlockError::TooManyNullifiersInBlock(nullifier_count));
172        }
173
174        let mut output_notes = BTreeSet::new();
175        let mut output_note_count = 0;
176        for batch in self.output_note_batches.iter() {
177            // TODO: We should construct blocks from something like `TransactionBatch` structs.
178            //       Then, we'll check that transaction batches have the right number of
179            //       nullifiers/output notes and we won't need to do such checks here.
180            if batch.len() > MAX_OUTPUT_NOTES_PER_BATCH {
181                return Err(BlockError::TooManyNotesInBatch(batch.len()));
182            }
183            output_note_count += batch.len();
184            for note in batch.iter() {
185                if !output_notes.insert(note.id()) {
186                    return Err(BlockError::DuplicateNoteFound(note.id()));
187                }
188            }
189        }
190
191        if output_note_count > MAX_OUTPUT_NOTES_PER_BLOCK {
192            return Err(BlockError::TooManyNotesInBlock(output_note_count));
193        }
194
195        Ok(())
196    }
197}
198
199impl Serializable for Block {
200    fn write_into<W: ByteWriter>(&self, target: &mut W) {
201        self.header.write_into(target);
202        self.updated_accounts.write_into(target);
203        self.output_note_batches.write_into(target);
204        self.nullifiers.write_into(target);
205    }
206}
207
208impl Deserializable for Block {
209    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
210        let block = Self {
211            header: BlockHeader::read_from(source)?,
212            updated_accounts: <Vec<BlockAccountUpdate>>::read_from(source)?,
213            output_note_batches: <Vec<NoteBatch>>::read_from(source)?,
214            nullifiers: <Vec<Nullifier>>::read_from(source)?,
215        };
216
217        block
218            .validate()
219            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
220
221        Ok(block)
222    }
223}
224
225// TRANSACTION HASH COMPUTATION
226// ================================================================================================
227
228/// Computes a commitment to the provided list of transactions.
229pub fn compute_tx_hash(
230    updated_accounts: impl Iterator<Item = (TransactionId, AccountId)>,
231) -> Digest {
232    let mut elements = vec![];
233    for (transaction_id, account_id) in updated_accounts {
234        let account_id_felts: [Felt; 2] = account_id.into();
235        elements.extend_from_slice(&[account_id_felts[0], account_id_felts[1], ZERO, ZERO]);
236        elements.extend_from_slice(transaction_id.as_elements());
237    }
238
239    Hasher::hash_elements(&elements)
240}
241
242// BLOCK ACCOUNT UPDATE
243// ================================================================================================
244
245/// Describes the changes made to an account state resulting from executing transactions contained
246/// in a block.
247#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct BlockAccountUpdate {
249    /// ID of the updated account.
250    account_id: AccountId,
251
252    /// Hash of the new state of the account.
253    new_state_hash: Digest,
254
255    /// A set of changes which can be applied to the previous account state (i.e., the state as of
256    /// the last block) to get the new account state. For private accounts, this is set to
257    /// [AccountUpdateDetails::Private].
258    details: AccountUpdateDetails,
259
260    /// IDs of all transactions in the block that updated the account.
261    transactions: Vec<TransactionId>,
262}
263
264impl BlockAccountUpdate {
265    /// Returns a new [BlockAccountUpdate] instantiated from the specified components.
266    pub const fn new(
267        account_id: AccountId,
268        new_state_hash: Digest,
269        details: AccountUpdateDetails,
270        transactions: Vec<TransactionId>,
271    ) -> Self {
272        Self {
273            account_id,
274            new_state_hash,
275            details,
276            transactions,
277        }
278    }
279
280    /// Returns the ID of the updated account.
281    pub fn account_id(&self) -> AccountId {
282        self.account_id
283    }
284
285    /// Returns the hash of the new account state.
286    pub fn new_state_hash(&self) -> Digest {
287        self.new_state_hash
288    }
289
290    /// Returns the description of the updates for public accounts.
291    ///
292    /// These descriptions can be used to build the new account state from the previous account
293    /// state.
294    pub fn details(&self) -> &AccountUpdateDetails {
295        &self.details
296    }
297
298    /// Returns the IDs of all transactions in the block that updated the account.
299    pub fn transactions(&self) -> &[TransactionId] {
300        &self.transactions
301    }
302
303    /// Returns `true` if the account update details are for private account.
304    pub fn is_private(&self) -> bool {
305        self.details.is_private()
306    }
307}
308
309impl Serializable for BlockAccountUpdate {
310    fn write_into<W: ByteWriter>(&self, target: &mut W) {
311        self.account_id.write_into(target);
312        self.new_state_hash.write_into(target);
313        self.details.write_into(target);
314        self.transactions.write_into(target);
315    }
316}
317
318impl Deserializable for BlockAccountUpdate {
319    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
320        Ok(Self {
321            account_id: AccountId::read_from(source)?,
322            new_state_hash: Digest::read_from(source)?,
323            details: AccountUpdateDetails::read_from(source)?,
324            transactions: Vec::<TransactionId>::read_from(source)?,
325        })
326    }
327}