snarkvm_ledger_narwhal_batch_header/
lib.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![forbid(unsafe_code)]
17#![warn(clippy::cast_possible_truncation)]
18#![allow(clippy::too_many_arguments)]
19
20mod bytes;
21mod serialize;
22mod string;
23mod to_id;
24
25use console::{
26    account::{Address, PrivateKey, Signature},
27    prelude::*,
28    types::Field,
29};
30use narwhal_transmission_id::TransmissionID;
31
32use indexmap::IndexSet;
33
34#[cfg(not(feature = "serial"))]
35use rayon::prelude::*;
36
37#[derive(Clone, PartialEq, Eq)]
38pub struct BatchHeader<N: Network> {
39    /// The batch ID, defined as the hash of the author, round number, timestamp, transmission IDs,
40    /// committee ID, and previous batch certificate IDs.
41    batch_id: Field<N>,
42    /// The author of the batch.
43    author: Address<N>,
44    /// The round number.
45    round: u64,
46    /// The timestamp.
47    timestamp: i64,
48    /// The committee ID.
49    committee_id: Field<N>,
50    /// The set of `transmission IDs`.
51    transmission_ids: IndexSet<TransmissionID<N>>,
52    /// The batch certificate IDs of the previous round.
53    previous_certificate_ids: IndexSet<Field<N>>,
54    /// The signature of the batch ID from the creator.
55    signature: Signature<N>,
56}
57
58impl<N: Network> BatchHeader<N> {
59    /// The maximum number of rounds to store before garbage collecting.
60    pub const MAX_GC_ROUNDS: usize = 100;
61    /// The maximum number of transmissions in a batch.
62    /// Note: This limit is set to 50 as part of safety measures to prevent DoS attacks.
63    /// This limit can be increased in the future as performance improves. Alternatively,
64    /// the rate of block production can be sped up to compensate for the limit set here.
65    pub const MAX_TRANSMISSIONS_PER_BATCH: usize = 50;
66}
67
68impl<N: Network> BatchHeader<N> {
69    /// Initializes a new batch header.
70    pub fn new<R: Rng + CryptoRng>(
71        private_key: &PrivateKey<N>,
72        round: u64,
73        timestamp: i64,
74        committee_id: Field<N>,
75        transmission_ids: IndexSet<TransmissionID<N>>,
76        previous_certificate_ids: IndexSet<Field<N>>,
77        rng: &mut R,
78    ) -> Result<Self> {
79        match round {
80            0 | 1 => {
81                // If the round is zero or one, then there should be no previous certificate IDs.
82                ensure!(previous_certificate_ids.is_empty(), "Invalid round number, must not have certificates");
83            }
84            // If the round is not zero and not one, then there should be at least one previous certificate ID.
85            _ => ensure!(!previous_certificate_ids.is_empty(), "Invalid round number, must have certificates"),
86        }
87
88        // Ensure that the number of transmissions is within bounds.
89        ensure!(
90            transmission_ids.len() <= Self::MAX_TRANSMISSIONS_PER_BATCH,
91            "Invalid number of transmission IDs ({})",
92            transmission_ids.len()
93        );
94        // Ensure that the number of previous certificate IDs is within bounds.
95        ensure!(
96            previous_certificate_ids.len() <= N::LATEST_MAX_CERTIFICATES()? as usize,
97            "Invalid number of previous certificate IDs ({})",
98            previous_certificate_ids.len()
99        );
100
101        // Retrieve the address.
102        let author = Address::try_from(private_key)?;
103        // Compute the batch ID.
104        let batch_id = Self::compute_batch_id(
105            author,
106            round,
107            timestamp,
108            committee_id,
109            &transmission_ids,
110            &previous_certificate_ids,
111        )?;
112        // Sign the preimage.
113        let signature = private_key.sign(&[batch_id], rng)?;
114        // Return the batch header.
115        Ok(Self {
116            batch_id,
117            author,
118            round,
119            timestamp,
120            committee_id,
121            transmission_ids,
122            previous_certificate_ids,
123            signature,
124        })
125    }
126
127    /// Initializes a new batch header.
128    pub fn from(
129        author: Address<N>,
130        round: u64,
131        timestamp: i64,
132        committee_id: Field<N>,
133        transmission_ids: IndexSet<TransmissionID<N>>,
134        previous_certificate_ids: IndexSet<Field<N>>,
135        signature: Signature<N>,
136    ) -> Result<Self> {
137        match round {
138            0 | 1 => {
139                // If the round is zero or one, then there should be no previous certificate IDs.
140                ensure!(previous_certificate_ids.is_empty(), "Invalid round number, must not have certificates");
141            }
142            // If the round is not zero and not one, then there should be at least one previous certificate ID.
143            _ => ensure!(!previous_certificate_ids.is_empty(), "Invalid round number, must have certificates"),
144        }
145
146        // Ensure that the number of transmissions is within bounds.
147        ensure!(
148            transmission_ids.len() <= Self::MAX_TRANSMISSIONS_PER_BATCH,
149            "Invalid number of transmission IDs ({})",
150            transmission_ids.len()
151        );
152        // Ensure that the number of previous certificate IDs is within bounds.
153        ensure!(
154            previous_certificate_ids.len() <= N::LATEST_MAX_CERTIFICATES()? as usize,
155            "Invalid number of previous certificate IDs ({})",
156            previous_certificate_ids.len()
157        );
158
159        // Compute the batch ID.
160        let batch_id = Self::compute_batch_id(
161            author,
162            round,
163            timestamp,
164            committee_id,
165            &transmission_ids,
166            &previous_certificate_ids,
167        )?;
168        // Verify the signature.
169        if !signature.verify(&author, &[batch_id]) {
170            bail!("Invalid signature for the batch header");
171        }
172        // Return the batch header.
173        Ok(Self {
174            author,
175            batch_id,
176            round,
177            timestamp,
178            committee_id,
179            transmission_ids,
180            previous_certificate_ids,
181            signature,
182        })
183    }
184}
185
186impl<N: Network> BatchHeader<N> {
187    /// Returns the batch ID.
188    pub const fn batch_id(&self) -> Field<N> {
189        self.batch_id
190    }
191
192    /// Returns the author.
193    pub const fn author(&self) -> Address<N> {
194        self.author
195    }
196
197    /// Returns the round number.
198    pub const fn round(&self) -> u64 {
199        self.round
200    }
201
202    /// Returns the timestamp.
203    pub const fn timestamp(&self) -> i64 {
204        self.timestamp
205    }
206
207    /// Returns the committee ID.
208    pub const fn committee_id(&self) -> Field<N> {
209        self.committee_id
210    }
211
212    /// Returns the transmission IDs.
213    pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
214        &self.transmission_ids
215    }
216
217    /// Returns the batch certificate IDs for the previous round.
218    pub const fn previous_certificate_ids(&self) -> &IndexSet<Field<N>> {
219        &self.previous_certificate_ids
220    }
221
222    /// Returns the signature.
223    pub const fn signature(&self) -> &Signature<N> {
224        &self.signature
225    }
226}
227
228impl<N: Network> BatchHeader<N> {
229    /// Returns `true` if the batch header is empty.
230    pub fn is_empty(&self) -> bool {
231        self.transmission_ids.is_empty()
232    }
233
234    /// Returns the number of transmissions in the batch header.
235    pub fn len(&self) -> usize {
236        self.transmission_ids.len()
237    }
238
239    /// Returns `true` if the batch contains the specified `transmission ID`.
240    pub fn contains(&self, transmission_id: impl Into<TransmissionID<N>>) -> bool {
241        self.transmission_ids.contains(&transmission_id.into())
242    }
243}
244
245#[cfg(any(test, feature = "test-helpers"))]
246pub mod test_helpers {
247    use super::*;
248    use console::{account::PrivateKey, network::MainnetV0, prelude::TestRng};
249
250    use time::OffsetDateTime;
251
252    type CurrentNetwork = MainnetV0;
253
254    /// Returns a sample batch header, sampled at random.
255    pub fn sample_batch_header(rng: &mut TestRng) -> BatchHeader<CurrentNetwork> {
256        sample_batch_header_for_round(rng.gen(), rng)
257    }
258
259    /// Returns a sample batch header with a given round; the rest is sampled at random.
260    pub fn sample_batch_header_for_round(round: u64, rng: &mut TestRng) -> BatchHeader<CurrentNetwork> {
261        // Sample certificate IDs.
262        let certificate_ids = (0..10).map(|_| Field::<CurrentNetwork>::rand(rng)).collect::<IndexSet<_>>();
263        // Return the batch header.
264        sample_batch_header_for_round_with_previous_certificate_ids(round, certificate_ids, rng)
265    }
266
267    /// Returns a sample batch header with a given round and set of previous certificate IDs; the rest is sampled at random.
268    pub fn sample_batch_header_for_round_with_previous_certificate_ids(
269        round: u64,
270        previous_certificate_ids: IndexSet<Field<CurrentNetwork>>,
271        rng: &mut TestRng,
272    ) -> BatchHeader<CurrentNetwork> {
273        // Sample a private key.
274        let private_key = PrivateKey::new(rng).unwrap();
275        // Sample the committee ID.
276        let committee_id = Field::<CurrentNetwork>::rand(rng);
277        // Sample transmission IDs.
278        let transmission_ids =
279            narwhal_transmission_id::test_helpers::sample_transmission_ids(rng).into_iter().collect::<IndexSet<_>>();
280        // Checkpoint the timestamp for the batch.
281        let timestamp = OffsetDateTime::now_utc().unix_timestamp();
282        // Return the batch header.
283        BatchHeader::new(&private_key, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, rng)
284            .unwrap()
285    }
286
287    /// Returns a list of sample batch headers, sampled at random.
288    pub fn sample_batch_headers(rng: &mut TestRng) -> Vec<BatchHeader<CurrentNetwork>> {
289        // Initialize a sample vector.
290        let mut sample = Vec::with_capacity(10);
291        // Append sample batches.
292        for _ in 0..10 {
293            // Append the batch header.
294            sample.push(sample_batch_header(rng));
295        }
296        // Return the sample vector.
297        sample
298    }
299}