snarkvm_ledger_narwhal_batch_certificate/
lib.rs1#![forbid(unsafe_code)]
17#![warn(clippy::cast_possible_truncation)]
18
19extern crate snarkvm_console as console;
20
21mod bytes;
22mod serialize;
23mod string;
24
25use console::{
26 account::{Address, Signature},
27 prelude::*,
28 types::Field,
29};
30use snarkvm_ledger_narwhal_batch_header::BatchHeader;
31use snarkvm_ledger_narwhal_transmission_id::TransmissionID;
32
33use core::hash::{Hash, Hasher};
34use indexmap::IndexSet;
35use std::{collections::HashSet, sync::OnceLock};
36
37#[cfg(not(feature = "serial"))]
38use rayon::prelude::*;
39
40#[derive(Clone)]
41pub struct BatchCertificate<N: Network> {
42 batch_header: BatchHeader<N>,
44 signatures: IndexSet<Signature<N>>,
46 signers: OnceLock<Vec<Address<N>>>,
57}
58
59impl<N: Network> BatchCertificate<N> {
60 pub fn max_signatures() -> u16 {
62 N::LATEST_MAX_CERTIFICATES()
63 }
64}
65
66impl<N: Network> BatchCertificate<N> {
67 pub fn from(batch_header: BatchHeader<N>, signatures: IndexSet<Signature<N>>) -> Result<Self> {
69 ensure!(signatures.len() <= Self::max_signatures() as usize, "Invalid number of signatures");
71
72 let signature_list = signatures.iter().collect::<Vec<_>>();
75
76 let signers = cfg_iter!(signature_list).map(|signature| signature.to_address()).collect::<Vec<_>>();
82
83 let signature_authors = signers.iter().copied().collect::<HashSet<_>>();
85 ensure!(
86 !signature_authors.contains(&batch_header.author()),
87 "The author's signature was included in the signers"
88 );
89 ensure!(signature_authors.len() == signatures.len(), "A duplicate author was found in the set of signatures");
90
91 cfg_iter!(signature_list).zip(&signers).try_for_each(|(signature, signer)| {
93 if !signature.verify(signer, &[batch_header.batch_id()]) {
94 bail!("Invalid batch certificate signature")
95 }
96 Ok(())
97 })?;
98
99 drop(signature_list);
101
102 let certificate = Self::from_unchecked(batch_header, signatures)?;
104 let _ = certificate.signers.set(signers);
107 Ok(certificate)
108 }
109
110 pub fn from_unchecked(batch_header: BatchHeader<N>, signatures: IndexSet<Signature<N>>) -> Result<Self> {
112 ensure!(!signatures.is_empty(), "Batch certificate must contain signatures");
114 Ok(Self { batch_header, signatures, signers: OnceLock::new() })
117 }
118}
119
120impl<N: Network> PartialEq for BatchCertificate<N> {
121 fn eq(&self, other: &Self) -> bool {
122 self.batch_id() == other.batch_id()
123 }
124}
125
126impl<N: Network> Eq for BatchCertificate<N> {}
127
128impl<N: Network> Hash for BatchCertificate<N> {
129 fn hash<H: Hasher>(&self, state: &mut H) {
130 self.batch_header.batch_id().hash(state);
131 }
132}
133
134impl<N: Network> BatchCertificate<N> {
135 pub const fn id(&self) -> Field<N> {
137 self.batch_header.batch_id()
138 }
139
140 pub const fn batch_header(&self) -> &BatchHeader<N> {
142 &self.batch_header
143 }
144
145 pub const fn batch_id(&self) -> Field<N> {
147 self.batch_header().batch_id()
148 }
149
150 pub const fn author(&self) -> Address<N> {
152 self.batch_header().author()
153 }
154
155 pub const fn round(&self) -> u64 {
157 self.batch_header().round()
158 }
159
160 pub fn timestamp(&self) -> i64 {
162 self.batch_header().timestamp()
163 }
164
165 pub const fn committee_id(&self) -> Field<N> {
167 self.batch_header().committee_id()
168 }
169
170 pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
172 self.batch_header().transmission_ids()
173 }
174
175 pub const fn previous_certificate_ids(&self) -> &IndexSet<Field<N>> {
177 self.batch_header().previous_certificate_ids()
178 }
179
180 pub fn signatures(&self) -> Box<dyn '_ + ExactSizeIterator<Item = &Signature<N>>> {
182 Box::new(self.signatures.iter())
183 }
184
185 pub fn signers(&self) -> &[Address<N>] {
199 self.signers.get_or_init(|| self.signatures.iter().map(|signature| signature.to_address()).collect())
200 }
201}
202
203#[cfg(any(test, feature = "test-helpers"))]
204pub mod test_helpers {
205 use super::*;
206 use console::{account::PrivateKey, network::MainnetV0, prelude::TestRng, types::Field};
207
208 use indexmap::IndexSet;
209
210 type CurrentNetwork = MainnetV0;
211
212 pub fn sample_batch_certificate(rng: &mut TestRng) -> BatchCertificate<CurrentNetwork> {
214 sample_batch_certificate_for_round(rng.random(), rng)
215 }
216
217 pub fn sample_batch_certificate_for_round(round: u64, rng: &mut TestRng) -> BatchCertificate<CurrentNetwork> {
219 let certificate_ids = (0..10).map(|_| Field::<CurrentNetwork>::rand(rng)).collect::<IndexSet<_>>();
221 sample_batch_certificate_for_round_with_previous_certificate_ids(round, certificate_ids, rng)
223 }
224
225 pub fn sample_batch_certificate_for_round_with_previous_certificate_ids(
227 round: u64,
228 previous_certificate_ids: IndexSet<Field<CurrentNetwork>>,
229 rng: &mut TestRng,
230 ) -> BatchCertificate<CurrentNetwork> {
231 let committee: Vec<_> = (0..5).map(|_| PrivateKey::new(rng).unwrap()).collect();
232 sample_batch_certificate_for_round_with_committee(
233 round,
234 previous_certificate_ids,
235 &committee[0],
236 &committee[1..],
237 rng,
238 )
239 }
240
241 pub fn sample_batch_certificate_for_round_with_committee(
243 round: u64,
244 previous_certificate_ids: IndexSet<Field<CurrentNetwork>>,
245 author: &PrivateKey<CurrentNetwork>,
246 signers: &[PrivateKey<CurrentNetwork>],
247 rng: &mut TestRng,
248 ) -> BatchCertificate<CurrentNetwork> {
249 let batch_header =
251 snarkvm_ledger_narwhal_batch_header::test_helpers::sample_batch_header_for_round_and_key_with_previous_certificate_ids(
252 round,
253 author,
254 previous_certificate_ids,
255 rng,
256 );
257 let signatures: IndexSet<_> =
259 signers.iter().map(|private_key| private_key.sign(&[batch_header.batch_id()], rng).unwrap()).collect();
260
261 BatchCertificate::from(batch_header, signatures).unwrap()
263 }
264
265 pub fn sample_batch_certificates(rng: &mut TestRng) -> IndexSet<BatchCertificate<CurrentNetwork>> {
267 let mut sample = IndexSet::with_capacity(10);
269 for _ in 0..10 {
271 sample.insert(sample_batch_certificate(rng));
272 }
273 sample
275 }
276
277 pub fn sample_batch_certificate_with_previous_certificates(
279 round: u64,
280 rng: &mut TestRng,
281 ) -> (BatchCertificate<CurrentNetwork>, Vec<BatchCertificate<CurrentNetwork>>) {
282 assert!(round > 1, "Round must be greater than 1");
283
284 let previous_round = round - 1; let current_round = round;
287
288 assert_eq!(previous_round % 2, 0, "Previous round must be even");
289
290 let previous_certificates = vec![
292 sample_batch_certificate_for_round(previous_round, rng),
293 sample_batch_certificate_for_round(previous_round, rng),
294 sample_batch_certificate_for_round(previous_round, rng),
295 sample_batch_certificate_for_round(previous_round, rng),
296 ];
297 let previous_certificate_ids: IndexSet<_> = previous_certificates.iter().map(|c| c.id()).collect();
299 let certificate = sample_batch_certificate_for_round_with_previous_certificate_ids(
301 current_round,
302 previous_certificate_ids,
303 rng,
304 );
305
306 (certificate, previous_certificates)
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use console::{network::MainnetV0, prelude::TestRng};
314
315 type CurrentNetwork = MainnetV0;
316
317 fn recompute_signers(certificate: &BatchCertificate<CurrentNetwork>) -> Vec<Address<CurrentNetwork>> {
319 certificate.signatures().map(|signature| signature.to_address()).collect()
320 }
321
322 #[test]
323 fn test_signers_matches_recomputation() {
324 let rng = &mut TestRng::default();
325
326 for _ in 0..8 {
327 let certificate = test_helpers::sample_batch_certificate(rng);
328 assert_eq!(certificate.signers(), recompute_signers(&certificate));
331 }
332 }
333
334 #[test]
335 fn test_signers_excludes_the_author() {
336 let rng = &mut TestRng::default();
337
338 let certificate = test_helpers::sample_batch_certificate(rng);
339 assert!(!certificate.signers().is_empty());
340 assert!(!certificate.signers().contains(&certificate.author()));
342 }
343
344 #[test]
345 fn test_signers_is_lazily_populated_after_deserialization() {
346 let rng = &mut TestRng::default();
347
348 let certificate = test_helpers::sample_batch_certificate(rng);
349 let expected = certificate.signers().to_vec();
350
351 let bytes = certificate.to_bytes_le().unwrap();
354 let recovered = BatchCertificate::<CurrentNetwork>::read_le_unchecked(&bytes[..]).unwrap();
355 assert!(recovered.signers.get().is_none(), "the cache should start out empty");
356
357 assert_eq!(recovered.signers(), expected);
358 assert_eq!(recovered.signers(), expected);
360 }
361
362 #[test]
363 fn test_signers_survives_cloning() {
364 let rng = &mut TestRng::default();
365
366 let certificate = test_helpers::sample_batch_certificate(rng);
367 let expected = certificate.signers().to_vec();
368
369 assert_eq!(certificate.clone().signers(), expected);
371
372 let bytes = certificate.to_bytes_le().unwrap();
374 let unpopulated = BatchCertificate::<CurrentNetwork>::read_le_unchecked(&bytes[..]).unwrap();
375 let cloned = unpopulated.clone();
376 assert!(cloned.signers.get().is_none(), "cloning must not populate the cache");
377 assert_eq!(cloned.signers(), expected);
378 }
379}