snarkvm_ledger_narwhal_batch_header/
lib.rs1#![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 batch_id: Field<N>,
42 author: Address<N>,
44 round: u64,
46 timestamp: i64,
48 committee_id: Field<N>,
50 transmission_ids: IndexSet<TransmissionID<N>>,
52 previous_certificate_ids: IndexSet<Field<N>>,
54 signature: Signature<N>,
56}
57
58impl<N: Network> BatchHeader<N> {
59 pub const MAX_GC_ROUNDS: usize = 100;
61 pub const MAX_TRANSMISSIONS_PER_BATCH: usize = 50;
66}
67
68impl<N: Network> BatchHeader<N> {
69 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 ensure!(previous_certificate_ids.is_empty(), "Invalid round number, must not have certificates");
83 }
84 _ => ensure!(!previous_certificate_ids.is_empty(), "Invalid round number, must have certificates"),
86 }
87
88 ensure!(
90 transmission_ids.len() <= Self::MAX_TRANSMISSIONS_PER_BATCH,
91 "Invalid number of transmission IDs ({})",
92 transmission_ids.len()
93 );
94 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 let author = Address::try_from(private_key)?;
103 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 let signature = private_key.sign(&[batch_id], rng)?;
114 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 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 ensure!(previous_certificate_ids.is_empty(), "Invalid round number, must not have certificates");
141 }
142 _ => ensure!(!previous_certificate_ids.is_empty(), "Invalid round number, must have certificates"),
144 }
145
146 ensure!(
148 transmission_ids.len() <= Self::MAX_TRANSMISSIONS_PER_BATCH,
149 "Invalid number of transmission IDs ({})",
150 transmission_ids.len()
151 );
152 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 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 if !signature.verify(&author, &[batch_id]) {
170 bail!("Invalid signature for the batch header");
171 }
172 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 pub const fn batch_id(&self) -> Field<N> {
189 self.batch_id
190 }
191
192 pub const fn author(&self) -> Address<N> {
194 self.author
195 }
196
197 pub const fn round(&self) -> u64 {
199 self.round
200 }
201
202 pub const fn timestamp(&self) -> i64 {
204 self.timestamp
205 }
206
207 pub const fn committee_id(&self) -> Field<N> {
209 self.committee_id
210 }
211
212 pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
214 &self.transmission_ids
215 }
216
217 pub const fn previous_certificate_ids(&self) -> &IndexSet<Field<N>> {
219 &self.previous_certificate_ids
220 }
221
222 pub const fn signature(&self) -> &Signature<N> {
224 &self.signature
225 }
226}
227
228impl<N: Network> BatchHeader<N> {
229 pub fn is_empty(&self) -> bool {
231 self.transmission_ids.is_empty()
232 }
233
234 pub fn len(&self) -> usize {
236 self.transmission_ids.len()
237 }
238
239 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 pub fn sample_batch_header(rng: &mut TestRng) -> BatchHeader<CurrentNetwork> {
256 sample_batch_header_for_round(rng.gen(), rng)
257 }
258
259 pub fn sample_batch_header_for_round(round: u64, rng: &mut TestRng) -> BatchHeader<CurrentNetwork> {
261 let certificate_ids = (0..10).map(|_| Field::<CurrentNetwork>::rand(rng)).collect::<IndexSet<_>>();
263 sample_batch_header_for_round_with_previous_certificate_ids(round, certificate_ids, rng)
265 }
266
267 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 let private_key = PrivateKey::new(rng).unwrap();
275 let committee_id = Field::<CurrentNetwork>::rand(rng);
277 let transmission_ids =
279 narwhal_transmission_id::test_helpers::sample_transmission_ids(rng).into_iter().collect::<IndexSet<_>>();
280 let timestamp = OffsetDateTime::now_utc().unix_timestamp();
282 BatchHeader::new(&private_key, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, rng)
284 .unwrap()
285 }
286
287 pub fn sample_batch_headers(rng: &mut TestRng) -> Vec<BatchHeader<CurrentNetwork>> {
289 let mut sample = Vec::with_capacity(10);
291 for _ in 0..10 {
293 sample.push(sample_batch_header(rng));
295 }
296 sample
298 }
299}