1use std::{collections::BTreeMap, fmt::Debug, hash::Hash, marker::PhantomData, time::Duration};
10
11use alloy::primitives::U256;
12use hotshot_utils::anytrace::*;
13use jf_vid::{VidDisperse as JfVidDisperse, VidScheme};
14use serde::{Deserialize, Serialize};
15use tokio::{task::spawn_blocking, time::Instant};
16
17use super::ns_table::parse_ns_table;
18use crate::{
19 epoch_membership::{EpochMembership, EpochMembershipCoordinator},
20 impl_has_epoch,
21 message::Proposal,
22 simple_vote::HasEpoch,
23 stake_table::HSStakeTable,
24 traits::{
25 block_contents::EncodeBytes,
26 node_implementation::NodeType,
27 signature_key::{SignatureKey, StakeTableEntryType},
28 BlockPayload,
29 },
30 vid::{
31 advz::{advz_scheme, ADVZCommitment, ADVZCommon, ADVZScheme, ADVZShare},
32 avidm::{init_avidm_param, AvidMCommitment, AvidMCommon, AvidMScheme, AvidMShare},
33 },
34 vote::HasViewNumber,
35};
36
37impl_has_epoch!(
38 ADVZDisperse<TYPES>,
39 AvidMDisperse<TYPES>,
40 VidDisperseShare2<TYPES>
41);
42
43#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
45pub struct ADVZDisperse<TYPES: NodeType> {
46 pub view_number: TYPES::View,
48 pub epoch: Option<TYPES::Epoch>,
50 pub target_epoch: Option<TYPES::Epoch>,
52 pub payload_commitment: ADVZCommitment,
54 pub shares: BTreeMap<TYPES::SignatureKey, ADVZShare>,
56 pub common: ADVZCommon,
58}
59
60impl<TYPES: NodeType> HasViewNumber<TYPES> for ADVZDisperse<TYPES> {
61 fn view_number(&self) -> TYPES::View {
62 self.view_number
63 }
64}
65
66impl<TYPES: NodeType> ADVZDisperse<TYPES> {
67 async fn from_membership(
71 view_number: TYPES::View,
72 mut vid_disperse: JfVidDisperse<ADVZScheme>,
73 membership: &EpochMembershipCoordinator<TYPES>,
74 target_epoch: Option<TYPES::Epoch>,
75 data_epoch: Option<TYPES::Epoch>,
76 ) -> Self {
77 let shares = membership
78 .stake_table_for_epoch(target_epoch)
79 .await
80 .unwrap()
81 .stake_table()
82 .await
83 .iter()
84 .map(|entry| entry.stake_table_entry.public_key())
85 .map(|node| (node.clone(), vid_disperse.shares.remove(0)))
86 .collect();
87
88 Self {
89 view_number,
90 shares,
91 common: vid_disperse.common,
92 payload_commitment: vid_disperse.commit,
93 epoch: data_epoch,
94 target_epoch,
95 }
96 }
97
98 #[allow(clippy::panic)]
104 pub async fn calculate_vid_disperse(
105 payload: &TYPES::BlockPayload,
106 membership: &EpochMembershipCoordinator<TYPES>,
107 view: TYPES::View,
108 target_epoch: Option<TYPES::Epoch>,
109 data_epoch: Option<TYPES::Epoch>,
110 ) -> Result<(Self, Duration)> {
111 let num_nodes = membership
112 .stake_table_for_epoch(target_epoch)
113 .await?
114 .total_nodes()
115 .await;
116
117 let txns = payload.encode();
118
119 let now = Instant::now();
120 let vid_disperse = spawn_blocking(move || advz_scheme(num_nodes).disperse(&txns))
121 .await
122 .wrap()
123 .context(error!("Join error"))?
124 .wrap()
125 .context(|err| error!("Failed to calculate VID disperse. Error: {err}"))?;
126 let advz_scheme_duration = now.elapsed();
127
128 Ok((
129 Self::from_membership(view, vid_disperse, membership, target_epoch, data_epoch).await,
130 advz_scheme_duration,
131 ))
132 }
133
134 pub fn payload_byte_len(&self) -> u32 {
136 ADVZScheme::get_payload_byte_len(&self.common)
137 }
138}
139
140#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
141pub struct ADVZDisperseShare<TYPES: NodeType> {
143 pub view_number: TYPES::View,
145 pub payload_commitment: ADVZCommitment,
147 pub share: ADVZShare,
149 pub common: ADVZCommon,
151 pub recipient_key: TYPES::SignatureKey,
153}
154
155impl<TYPES: NodeType> HasViewNumber<TYPES> for ADVZDisperseShare<TYPES> {
156 fn view_number(&self) -> TYPES::View {
157 self.view_number
158 }
159}
160
161impl<TYPES: NodeType> ADVZDisperseShare<TYPES> {
162 pub fn from_advz_disperse(vid_disperse: ADVZDisperse<TYPES>) -> Vec<Self> {
164 vid_disperse
165 .shares
166 .into_iter()
167 .map(|(recipient_key, share)| Self {
168 share,
169 recipient_key,
170 view_number: vid_disperse.view_number,
171 common: vid_disperse.common.clone(),
172 payload_commitment: vid_disperse.payload_commitment,
173 })
174 .collect()
175 }
176
177 pub fn to_proposal(
179 self,
180 private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
181 ) -> Option<Proposal<TYPES, Self>> {
182 let Ok(signature) =
183 TYPES::SignatureKey::sign(private_key, self.payload_commitment.as_ref())
184 else {
185 tracing::error!("VID: failed to sign dispersal share payload");
186 return None;
187 };
188 Some(Proposal {
189 signature,
190 _pd: PhantomData,
191 data: self,
192 })
193 }
194
195 pub fn to_advz_disperse<'a, I>(mut it: I) -> Option<ADVZDisperse<TYPES>>
197 where
198 I: Iterator<Item = &'a Self>,
199 {
200 let first_vid_disperse_share = it.next()?.clone();
201 let mut share_map = BTreeMap::new();
202 share_map.insert(
203 first_vid_disperse_share.recipient_key,
204 first_vid_disperse_share.share,
205 );
206 let mut vid_disperse = ADVZDisperse {
207 view_number: first_vid_disperse_share.view_number,
208 epoch: None,
209 target_epoch: None,
210 payload_commitment: first_vid_disperse_share.payload_commitment,
211 common: first_vid_disperse_share.common,
212 shares: share_map,
213 };
214 let _ = it.map(|vid_disperse_share| {
215 vid_disperse.shares.insert(
216 vid_disperse_share.recipient_key.clone(),
217 vid_disperse_share.share.clone(),
218 )
219 });
220 Some(vid_disperse)
221 }
222
223 pub fn to_vid_share_proposals(
225 vid_disperse: ADVZDisperse<TYPES>,
226 signature: &<TYPES::SignatureKey as SignatureKey>::PureAssembledSignatureType,
227 ) -> Vec<Proposal<TYPES, Self>> {
228 vid_disperse
229 .shares
230 .into_iter()
231 .map(|(recipient_key, share)| Proposal {
232 data: Self {
233 share,
234 recipient_key,
235 view_number: vid_disperse.view_number,
236 common: vid_disperse.common.clone(),
237 payload_commitment: vid_disperse.payload_commitment,
238 },
239 signature: signature.clone(),
240 _pd: PhantomData,
241 })
242 .collect()
243 }
244
245 #[allow(clippy::result_unit_err)]
250 pub fn verify_share(&self, total_weight: usize) -> std::result::Result<(), ()> {
251 advz_scheme(total_weight)
252 .verify_share(&self.share, &self.common, &self.payload_commitment)
253 .unwrap_or(Err(()))
254 }
255
256 pub fn payload_byte_len(&self) -> u32 {
258 ADVZScheme::get_payload_byte_len(&self.common)
259 }
260}
261
262#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
264pub struct AvidMDisperse<TYPES: NodeType> {
265 pub view_number: TYPES::View,
267 pub epoch: Option<TYPES::Epoch>,
269 pub target_epoch: Option<TYPES::Epoch>,
271 pub payload_commitment: AvidMCommitment,
273 pub shares: BTreeMap<TYPES::SignatureKey, AvidMShare>,
275 pub payload_byte_len: usize,
277 pub common: AvidMCommon,
279}
280
281impl<TYPES: NodeType> HasViewNumber<TYPES> for AvidMDisperse<TYPES> {
282 fn view_number(&self) -> TYPES::View {
283 self.view_number
284 }
285}
286
287pub const VID_TARGET_TOTAL_STAKE: u32 = 1000;
289
290struct Weights {
292 weights: Vec<u32>,
294
295 total_weight: usize,
297}
298
299pub fn vid_total_weight<TYPES: NodeType>(
300 stake_table: &HSStakeTable<TYPES>,
301 epoch: Option<TYPES::Epoch>,
302) -> usize {
303 if epoch.is_none() {
304 stake_table
305 .iter()
306 .fold(U256::ZERO, |acc, entry| {
307 acc + entry.stake_table_entry.stake()
308 })
309 .to::<usize>()
310 } else {
311 approximate_weights(stake_table).total_weight
312 }
313}
314
315fn approximate_weights<TYPES: NodeType>(stake_table: &HSStakeTable<TYPES>) -> Weights {
316 let total_stake = stake_table.iter().fold(U256::ZERO, |acc, entry| {
317 acc + entry.stake_table_entry.stake()
318 });
319
320 let mut total_weight: usize = 0;
321
322 if total_stake <= U256::from(VID_TARGET_TOTAL_STAKE) {
324 let weights = stake_table
325 .iter()
326 .map(|entry| entry.stake_table_entry.stake().to::<u32>())
327 .collect();
328
329 total_weight = total_stake.to::<usize>();
331
332 Weights {
333 weights,
334 total_weight,
335 }
336 } else {
337 let weights = stake_table
338 .iter()
339 .map(|entry| {
340 let weight: U256 = ((entry.stake_table_entry.stake()
341 * U256::from(VID_TARGET_TOTAL_STAKE))
342 / total_stake)
343 + U256::ONE;
344
345 total_weight += weight.to::<usize>();
347
348 weight.to::<u32>()
351 })
352 .collect();
353
354 Weights {
355 weights,
356 total_weight,
357 }
358 }
359}
360
361impl<TYPES: NodeType> AvidMDisperse<TYPES> {
362 async fn from_membership(
366 view_number: TYPES::View,
367 commit: AvidMCommitment,
368 shares: &[AvidMShare],
369 common: AvidMCommon,
370 membership: &EpochMembership<TYPES>,
371 target_epoch: Option<TYPES::Epoch>,
372 data_epoch: Option<TYPES::Epoch>,
373 ) -> Self {
374 let payload_byte_len = shares[0].payload_byte_len();
375 let shares = membership
376 .coordinator
377 .stake_table_for_epoch(target_epoch)
378 .await
379 .unwrap()
380 .stake_table()
381 .await
382 .iter()
383 .map(|entry| entry.stake_table_entry.public_key())
384 .zip(shares)
385 .map(|(node, share)| (node.clone(), share.clone()))
386 .collect();
387
388 Self {
389 view_number,
390 shares,
391 payload_commitment: commit,
392 epoch: data_epoch,
393 target_epoch,
394 payload_byte_len,
395 common,
396 }
397 }
398
399 #[allow(clippy::panic)]
405 #[allow(clippy::single_range_in_vec_init)]
406 pub async fn calculate_vid_disperse(
407 payload: &TYPES::BlockPayload,
408 membership: &EpochMembershipCoordinator<TYPES>,
409 view: TYPES::View,
410 target_epoch: Option<TYPES::Epoch>,
411 data_epoch: Option<TYPES::Epoch>,
412 metadata: &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
413 ) -> Result<(Self, Duration)> {
414 let target_mem = membership.stake_table_for_epoch(target_epoch).await?;
415 let stake_table = target_mem.stake_table().await;
416 let approximate_weights = approximate_weights(&stake_table);
417
418 let txns = payload.encode();
419 let num_txns = txns.len();
420
421 let avidm_param = init_avidm_param(approximate_weights.total_weight)?;
422 let common = avidm_param.clone();
423
424 let ns_table = parse_ns_table(num_txns, &metadata.encode());
425 let ns_table_clone = ns_table.clone();
426
427 let now = Instant::now();
428 let (commit, shares) = spawn_blocking(move || {
429 AvidMScheme::ns_disperse(
430 &avidm_param,
431 &approximate_weights.weights,
432 &txns,
433 ns_table_clone,
434 )
435 })
436 .await
437 .wrap()
438 .context(error!("Join error"))?
439 .wrap()
440 .context(|err| error!("Failed to calculate VID disperse. Error: {err}"))?;
441 let ns_disperse_duration = now.elapsed();
442
443 Ok((
444 Self::from_membership(
445 view,
446 commit,
447 &shares,
448 common,
449 &target_mem,
450 target_epoch,
451 data_epoch,
452 )
453 .await,
454 ns_disperse_duration,
455 ))
456 }
457
458 pub fn payload_byte_len(&self) -> u32 {
460 self.payload_byte_len as u32
461 }
462}
463
464#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
465pub struct VidDisperseShare2<TYPES: NodeType> {
467 pub view_number: TYPES::View,
469 pub epoch: Option<TYPES::Epoch>,
471 pub target_epoch: Option<TYPES::Epoch>,
473 pub payload_commitment: AvidMCommitment,
475 pub share: AvidMShare,
477 pub recipient_key: TYPES::SignatureKey,
479 pub common: AvidMCommon,
481}
482
483impl<TYPES: NodeType> HasViewNumber<TYPES> for VidDisperseShare2<TYPES> {
484 fn view_number(&self) -> TYPES::View {
485 self.view_number
486 }
487}
488
489impl<TYPES: NodeType> VidDisperseShare2<TYPES> {
490 pub fn from_vid_disperse(vid_disperse: AvidMDisperse<TYPES>) -> Vec<Self> {
492 vid_disperse
493 .shares
494 .into_iter()
495 .map(|(recipient_key, share)| Self {
496 share,
497 recipient_key,
498 view_number: vid_disperse.view_number,
499 payload_commitment: vid_disperse.payload_commitment,
500 epoch: vid_disperse.epoch,
501 target_epoch: vid_disperse.target_epoch,
502 common: vid_disperse.common.clone(),
503 })
504 .collect()
505 }
506
507 pub fn to_proposal(
509 self,
510 private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
511 ) -> Option<Proposal<TYPES, Self>> {
512 let Ok(signature) =
513 TYPES::SignatureKey::sign(private_key, self.payload_commitment.as_ref())
514 else {
515 tracing::error!("VID: failed to sign dispersal share payload");
516 return None;
517 };
518 Some(Proposal {
519 signature,
520 _pd: PhantomData,
521 data: self,
522 })
523 }
524
525 pub fn to_vid_disperse<'a, I>(mut it: I) -> Option<AvidMDisperse<TYPES>>
527 where
528 I: Iterator<Item = &'a Self>,
529 {
530 let first_vid_disperse_share = it.next()?.clone();
531 let payload_byte_len = first_vid_disperse_share.share.payload_byte_len();
532 let mut share_map = BTreeMap::new();
533 share_map.insert(
534 first_vid_disperse_share.recipient_key,
535 first_vid_disperse_share.share,
536 );
537 let mut vid_disperse = AvidMDisperse {
538 view_number: first_vid_disperse_share.view_number,
539 epoch: first_vid_disperse_share.epoch,
540 target_epoch: first_vid_disperse_share.target_epoch,
541 payload_commitment: first_vid_disperse_share.payload_commitment,
542 shares: share_map,
543 payload_byte_len,
544 common: first_vid_disperse_share.common,
545 };
546 let _ = it.map(|vid_disperse_share| {
547 vid_disperse.shares.insert(
548 vid_disperse_share.recipient_key.clone(),
549 vid_disperse_share.share.clone(),
550 )
551 });
552 Some(vid_disperse)
553 }
554
555 pub fn payload_byte_len(&self) -> u32 {
557 self.share.payload_byte_len() as u32
558 }
559
560 pub fn to_vid_share_proposals(
562 vid_disperse: AvidMDisperse<TYPES>,
563 signature: &<TYPES::SignatureKey as SignatureKey>::PureAssembledSignatureType,
564 ) -> Vec<Proposal<TYPES, Self>> {
565 vid_disperse
566 .shares
567 .into_iter()
568 .map(|(recipient_key, share)| Proposal {
569 data: Self {
570 share,
571 recipient_key,
572 view_number: vid_disperse.view_number,
573 payload_commitment: vid_disperse.payload_commitment,
574 epoch: vid_disperse.epoch,
575 target_epoch: vid_disperse.target_epoch,
576 common: vid_disperse.common.clone(),
577 },
578 signature: signature.clone(),
579 _pd: PhantomData,
580 })
581 .collect()
582 }
583
584 #[allow(clippy::result_unit_err)]
588 pub fn verify_share(&self, total_weight: usize) -> std::result::Result<(), ()> {
589 let avidm_param = init_avidm_param(total_weight).map_err(|_| ())?;
590 AvidMScheme::verify_share(&avidm_param, &self.payload_commitment, &self.share)
591 .unwrap_or(Err(()))
592 }
593}