snarkvm_ledger_narwhal_batch_header/
lib.rs1#![forbid(unsafe_code)]
17#![warn(clippy::cast_possible_truncation)]
18#![allow(clippy::too_many_arguments)]
19
20extern crate snarkvm_console as console;
21
22mod bytes;
23mod serialize;
24mod string;
25mod to_id;
26
27use console::{
28 account::{Address, PrivateKey, Signature},
29 prelude::*,
30 types::Field,
31};
32use snarkvm_ledger_narwhal_transmission_id::TransmissionID;
33
34use indexmap::IndexSet;
35
36#[cfg(not(feature = "serial"))]
37use rayon::prelude::*;
38
39#[derive(Clone, PartialEq, Eq)]
40pub struct BatchHeader<N: Network> {
41 batch_id: Field<N>,
44 author: Address<N>,
46 round: u64,
48 timestamp: i64,
50 committee_id: Field<N>,
52 transmission_ids: IndexSet<TransmissionID<N>>,
54 previous_certificate_ids: IndexSet<Field<N>>,
56 signature: Signature<N>,
58}
59
60impl<N: Network> BatchHeader<N> {
61 pub const MAX_GC_ROUNDS: usize = 100;
63 pub const MAX_TRANSMISSIONS_PER_BATCH: usize = 50;
68}
69
70impl<N: Network> BatchHeader<N> {
71 pub fn batch_spend_limit(height: u32) -> u64 {
75 consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap() * Self::MAX_TRANSMISSIONS_PER_BATCH as u64
76 / 20
77 }
78}
79
80impl<N: Network> BatchHeader<N> {
81 pub fn new<R: Rng + CryptoRng>(
83 private_key: &PrivateKey<N>,
84 round: u64,
85 timestamp: i64,
86 committee_id: Field<N>,
87 transmission_ids: IndexSet<TransmissionID<N>>,
88 previous_certificate_ids: IndexSet<Field<N>>,
89 rng: &mut R,
90 ) -> Result<Self> {
91 match round {
92 0 | 1 => {
93 ensure!(previous_certificate_ids.is_empty(), "Invalid round number, must not have certificates");
95 }
96 _ => ensure!(!previous_certificate_ids.is_empty(), "Invalid round number, must have certificates"),
98 }
99
100 ensure!(
102 transmission_ids.len() <= Self::MAX_TRANSMISSIONS_PER_BATCH,
103 "Invalid number of transmission IDs ({})",
104 transmission_ids.len()
105 );
106 ensure!(
108 previous_certificate_ids.len() <= N::LATEST_MAX_CERTIFICATES()? as usize,
109 "Invalid number of previous certificate IDs ({})",
110 previous_certificate_ids.len()
111 );
112
113 let author = Address::try_from(private_key)?;
115 let batch_id = Self::compute_batch_id(
117 author,
118 round,
119 timestamp,
120 committee_id,
121 &transmission_ids,
122 &previous_certificate_ids,
123 )?;
124 let signature = private_key.sign(&[batch_id], rng)?;
126 Ok(Self {
128 batch_id,
129 author,
130 round,
131 timestamp,
132 committee_id,
133 transmission_ids,
134 previous_certificate_ids,
135 signature,
136 })
137 }
138
139 pub fn from(
141 author: Address<N>,
142 round: u64,
143 timestamp: i64,
144 committee_id: Field<N>,
145 transmission_ids: IndexSet<TransmissionID<N>>,
146 previous_certificate_ids: IndexSet<Field<N>>,
147 signature: Signature<N>,
148 ) -> Result<Self> {
149 match round {
150 0 | 1 => {
151 ensure!(previous_certificate_ids.is_empty(), "Invalid round number, must not have certificates");
153 }
154 _ => ensure!(!previous_certificate_ids.is_empty(), "Invalid round number, must have certificates"),
156 }
157
158 ensure!(
160 transmission_ids.len() <= Self::MAX_TRANSMISSIONS_PER_BATCH,
161 "Invalid number of transmission IDs ({})",
162 transmission_ids.len()
163 );
164 ensure!(
166 previous_certificate_ids.len() <= N::LATEST_MAX_CERTIFICATES()? as usize,
167 "Invalid number of previous certificate IDs ({})",
168 previous_certificate_ids.len()
169 );
170
171 let batch_id = Self::compute_batch_id(
173 author,
174 round,
175 timestamp,
176 committee_id,
177 &transmission_ids,
178 &previous_certificate_ids,
179 )?;
180 if !signature.verify(&author, &[batch_id]) {
182 bail!("Invalid signature for the batch header");
183 }
184 Ok(Self {
186 author,
187 batch_id,
188 round,
189 timestamp,
190 committee_id,
191 transmission_ids,
192 previous_certificate_ids,
193 signature,
194 })
195 }
196}
197
198impl<N: Network> BatchHeader<N> {
199 pub const fn batch_id(&self) -> Field<N> {
201 self.batch_id
202 }
203
204 pub const fn author(&self) -> Address<N> {
206 self.author
207 }
208
209 pub const fn round(&self) -> u64 {
211 self.round
212 }
213
214 pub const fn timestamp(&self) -> i64 {
216 self.timestamp
217 }
218
219 pub const fn committee_id(&self) -> Field<N> {
221 self.committee_id
222 }
223
224 pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
226 &self.transmission_ids
227 }
228
229 pub const fn previous_certificate_ids(&self) -> &IndexSet<Field<N>> {
231 &self.previous_certificate_ids
232 }
233
234 pub const fn signature(&self) -> &Signature<N> {
236 &self.signature
237 }
238}
239
240impl<N: Network> BatchHeader<N> {
241 pub fn is_empty(&self) -> bool {
243 self.transmission_ids.is_empty()
244 }
245
246 pub fn len(&self) -> usize {
248 self.transmission_ids.len()
249 }
250
251 pub fn contains(&self, transmission_id: impl Into<TransmissionID<N>>) -> bool {
253 self.transmission_ids.contains(&transmission_id.into())
254 }
255}
256
257#[cfg(any(test, feature = "test-helpers"))]
258pub mod test_helpers {
259 use super::*;
260 use console::{account::PrivateKey, network::MainnetV0, prelude::TestRng};
261
262 use time::OffsetDateTime;
263
264 type CurrentNetwork = MainnetV0;
265
266 pub fn sample_batch_header(rng: &mut TestRng) -> BatchHeader<CurrentNetwork> {
268 sample_batch_header_for_round(rng.r#gen(), rng)
269 }
270
271 pub fn sample_batch_header_for_round(round: u64, rng: &mut TestRng) -> BatchHeader<CurrentNetwork> {
273 let certificate_ids = (0..10).map(|_| Field::<CurrentNetwork>::rand(rng)).collect::<IndexSet<_>>();
275 sample_batch_header_for_round_with_previous_certificate_ids(round, certificate_ids, rng)
277 }
278
279 pub fn sample_batch_header_for_round_with_previous_certificate_ids(
281 round: u64,
282 previous_certificate_ids: IndexSet<Field<CurrentNetwork>>,
283 rng: &mut TestRng,
284 ) -> BatchHeader<CurrentNetwork> {
285 let private_key = PrivateKey::new(rng).unwrap();
287 sample_batch_header_for_round_and_key_with_previous_certificate_ids(
289 round,
290 &private_key,
291 previous_certificate_ids,
292 rng,
293 )
294 }
295
296 pub fn sample_batch_header_for_round_and_key_with_previous_certificate_ids(
298 round: u64,
299 private_key: &PrivateKey<CurrentNetwork>,
300 previous_certificate_ids: IndexSet<Field<CurrentNetwork>>,
301 rng: &mut TestRng,
302 ) -> BatchHeader<CurrentNetwork> {
303 let committee_id = Field::<CurrentNetwork>::rand(rng);
305 let transmission_ids = snarkvm_ledger_narwhal_transmission_id::test_helpers::sample_transmission_ids(rng)
307 .into_iter()
308 .collect::<IndexSet<_>>();
309 let timestamp = OffsetDateTime::now_utc().unix_timestamp();
311 BatchHeader::new(private_key, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, rng)
313 .unwrap()
314 }
315
316 pub fn sample_batch_headers(rng: &mut TestRng) -> Vec<BatchHeader<CurrentNetwork>> {
318 let mut sample = Vec::with_capacity(10);
320 for _ in 0..10 {
322 sample.push(sample_batch_header(rng));
324 }
325 sample
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 use console::network::{CanaryV0, MainnetV0, TestnetV0};
335
336 #[test]
337 fn test_max_synthesis_cost_below_batch_spend_limit() {
338 fn max_synthesis_cost_valid<N: Network>() {
339 let max_synthesis_cost = N::MAX_DEPLOYMENT_VARIABLES.saturating_add(N::MAX_DEPLOYMENT_CONSTRAINTS)
340 * N::SYNTHESIS_FEE_MULTIPLIER
341 / N::ARC_0005_COMPUTE_DISCOUNT;
342 for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
343 assert!(max_synthesis_cost < BatchHeader::<N>::batch_spend_limit(*height));
344 }
345 }
346
347 max_synthesis_cost_valid::<CanaryV0>();
348 max_synthesis_cost_valid::<TestnetV0>();
349 max_synthesis_cost_valid::<MainnetV0>();
350 }
351}