hotshot_new_protocol/vid/
fragments.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use hotshot_types::{
4 data::{
5 EpochNumber, VidCommitment2, VidDisperseShare2, ViewNumber,
6 vid_disperse::{AvidmGf2DisperseShareFragment, AvidmGf2NamespacePiece},
7 },
8 traits::node_implementation::NodeType,
9 vid::avidm_gf2::{AvidmGf2Common, AvidmGf2Param},
10};
11
12pub struct VidFragmentAccumulator<T: NodeType> {
13 pending: BTreeMap<(ViewNumber, EpochNumber), BTreeMap<T::SignatureKey, PendingShare<T>>>,
17 completed: BTreeMap<(ViewNumber, EpochNumber), BTreeSet<T::SignatureKey>>,
18 lower_bound: ViewNumber,
19}
20
21#[derive(Debug, thiserror::Error)]
22pub enum VidFragmentError {
23 #[error("fragment disagrees with the view's pinned metadata")]
24 Inconsistent,
25
26 #[error("namespace index {index} out of range for {num_namespaces} namespaces")]
27 IndexOutOfRange { index: usize, num_namespaces: usize },
28
29 #[error("duplicate fragment for namespace index {0}")]
30 DuplicateIndex(usize),
31
32 #[error("fragment contains no namespaces")]
33 Empty,
34
35 #[error("fragment names no epoch")]
36 MissingEpoch,
37
38 #[error("fragment target epoch {target_epoch:?} does not match its epoch {epoch:?}")]
39 TargetEpochMismatch {
40 epoch: Option<EpochNumber>,
41 target_epoch: Option<EpochNumber>,
42 },
43}
44
45impl<T: NodeType> Default for VidFragmentAccumulator<T> {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl<T: NodeType> VidFragmentAccumulator<T> {
52 pub fn new() -> Self {
53 Self {
54 pending: BTreeMap::new(),
55 completed: BTreeMap::new(),
56 lower_bound: ViewNumber::genesis(),
57 }
58 }
59
60 pub fn accept(
73 &mut self,
74 disperser: &T::SignatureKey,
75 fragment: AvidmGf2DisperseShareFragment<T>,
76 ) -> Result<Option<VidDisperseShare2<T>>, VidFragmentError> {
77 let view = fragment.view_number;
78 let epoch = well_formed(&fragment)?;
79 if self.is_retired(view, epoch, disperser) {
80 return Ok(None);
81 }
82 let complete = {
83 let pending = self.pending_for(view, epoch, disperser, &fragment)?;
84 pending.insert_pieces(fragment.namespaces)?;
85 pending.is_complete()
86 };
87 if !complete {
88 return Ok(None);
89 }
90 let pending = self.take_pending(view, epoch, disperser);
91 self.completed
92 .entry((view, epoch))
93 .or_default()
94 .insert(disperser.clone());
95 Ok(Some(pending.into_share(view)))
96 }
97
98 pub fn gc(&mut self, view_number: ViewNumber) {
99 let cutoff = (view_number, EpochNumber::new(0));
100 self.pending = self.pending.split_off(&cutoff);
101 self.completed = self.completed.split_off(&cutoff);
102 self.lower_bound = view_number;
103 }
104
105 fn is_retired(
108 &self,
109 view: ViewNumber,
110 epoch: EpochNumber,
111 disperser: &T::SignatureKey,
112 ) -> bool {
113 view < self.lower_bound
114 || self
115 .completed
116 .get(&(view, epoch))
117 .is_some_and(|keys| keys.contains(disperser))
118 }
119
120 fn pending_for<'a>(
124 &'a mut self,
125 view: ViewNumber,
126 epoch: EpochNumber,
127 disperser: &T::SignatureKey,
128 fragment: &AvidmGf2DisperseShareFragment<T>,
129 ) -> Result<&'a mut PendingShare<T>, VidFragmentError> {
130 let pending = self
131 .pending
132 .entry((view, epoch))
133 .or_default()
134 .entry(disperser.clone())
135 .or_insert_with(|| PendingShare {
136 epoch,
137 payload_commitment: fragment.payload_commitment,
138 recipient_key: fragment.recipient_key.clone(),
139 param: fragment.param.clone(),
140 num_namespaces: fragment.num_namespaces,
141 pieces: BTreeMap::new(),
142 });
143 if pending.num_namespaces != fragment.num_namespaces
144 || pending.payload_commitment != fragment.payload_commitment
145 || pending.recipient_key != fragment.recipient_key
146 || pending.param != fragment.param
147 {
148 return Err(VidFragmentError::Inconsistent);
149 }
150 Ok(pending)
151 }
152
153 fn take_pending(
156 &mut self,
157 view: ViewNumber,
158 epoch: EpochNumber,
159 disperser: &T::SignatureKey,
160 ) -> PendingShare<T> {
161 let by_disperser = self
162 .pending
163 .get_mut(&(view, epoch))
164 .expect("buffer just opened above");
165 let pending = by_disperser
166 .remove(disperser)
167 .expect("buffer just opened above");
168 if by_disperser.is_empty() {
169 self.pending.remove(&(view, epoch));
170 }
171 pending
172 }
173}
174
175fn well_formed<T: NodeType>(
183 fragment: &AvidmGf2DisperseShareFragment<T>,
184) -> Result<EpochNumber, VidFragmentError> {
185 if fragment.num_namespaces == 0 {
186 return Err(VidFragmentError::Empty);
187 }
188 if fragment.target_epoch != fragment.epoch {
189 return Err(VidFragmentError::TargetEpochMismatch {
190 epoch: fragment.epoch,
191 target_epoch: fragment.target_epoch,
192 });
193 }
194 fragment.epoch.ok_or(VidFragmentError::MissingEpoch)
195}
196
197struct PendingShare<T: NodeType> {
199 epoch: EpochNumber,
200 payload_commitment: VidCommitment2,
201 recipient_key: T::SignatureKey,
202 param: AvidmGf2Param,
203 num_namespaces: usize,
204 pieces: BTreeMap<usize, AvidmGf2NamespacePiece>,
205}
206
207impl<T: NodeType> PendingShare<T> {
208 fn insert_pieces(
209 &mut self,
210 pieces: Vec<AvidmGf2NamespacePiece>,
211 ) -> Result<(), VidFragmentError> {
212 let mut incoming = BTreeSet::new();
213 for piece in &pieces {
214 let ns_index = piece.ns_index;
215 if ns_index >= self.num_namespaces {
216 return Err(VidFragmentError::IndexOutOfRange {
217 index: ns_index,
218 num_namespaces: self.num_namespaces,
219 });
220 }
221 if self.pieces.contains_key(&ns_index) || !incoming.insert(ns_index) {
222 return Err(VidFragmentError::DuplicateIndex(ns_index));
223 }
224 }
225 for piece in pieces {
226 self.pieces.insert(piece.ns_index, piece);
227 }
228 Ok(())
229 }
230
231 fn is_complete(&self) -> bool {
232 self.pieces.len() == self.num_namespaces
233 }
234
235 fn into_share(self, view: ViewNumber) -> VidDisperseShare2<T> {
241 let mut ns_commits = Vec::with_capacity(self.num_namespaces);
242 let mut ns_lens = Vec::with_capacity(self.num_namespaces);
243 let mut ns_shares = Vec::with_capacity(self.num_namespaces);
244 for piece in self.pieces.into_values() {
245 ns_commits.push(piece.ns_commit);
246 ns_lens.push(piece.ns_payload_byte_len);
247 ns_shares.push(piece.ns_share);
248 }
249 VidDisperseShare2 {
250 view_number: view,
251 epoch: Some(self.epoch),
252 target_epoch: Some(self.epoch),
255 payload_commitment: self.payload_commitment,
256 share: ns_shares.into(),
257 recipient_key: self.recipient_key,
258 common: AvidmGf2Common {
259 param: self.param,
260 ns_commits,
261 ns_lens,
262 },
263 }
264 }
265}