commonware_consensus/marshal/core/
floor.rs1use crate::{
2 marshal::resolver::handler::{Annotation, Key, Request},
3 simplex::types::Finalization,
4 types::{Height, Round},
5};
6use commonware_cryptography::{Digest, certificate::Scheme};
7use commonware_resolver::{Resolver, TargetedResolver};
8use commonware_utils::vec::NonEmptyVec;
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct Floor {
16 height: Option<Height>,
17 round: Round,
18}
19
20impl Floor {
21 pub const fn height(&self) -> Option<Height> {
23 self.height
24 }
25
26 pub const fn round(&self) -> Round {
28 self.round
29 }
30}
31
32pub(super) struct State<S: Scheme, C: Digest> {
34 height: Option<Height>,
35 round: Round,
36 pending: Option<Finalization<S, C>>,
37}
38
39impl<S: Scheme, C: Digest> State<S, C> {
40 pub(super) const fn resolved(height: Option<Height>, round: Round) -> Self {
41 Self {
42 height,
43 round,
44 pending: None,
45 }
46 }
47
48 pub(super) const fn awaiting_anchor(
49 height: Option<Height>,
50 round: Round,
51 finalization: Finalization<S, C>,
52 ) -> Self {
53 Self {
54 height,
55 round,
56 pending: Some(finalization),
57 }
58 }
59
60 pub(super) const fn snapshot(&self) -> Floor {
61 Floor {
62 height: self.height,
63 round: self.round,
64 }
65 }
66
67 pub(super) const fn processed_height(&self) -> Height {
75 match self.height {
76 Some(height) => height,
77 None => Height::zero(),
78 }
79 }
80
81 pub(super) const fn round(&self) -> Round {
82 self.round
83 }
84
85 pub(super) const fn set_processed_height(&mut self, height: Height) {
86 self.height = Some(height);
87 }
88
89 pub(super) const fn set_processed_round(&mut self, round: Round) {
90 self.round = round;
91 }
92
93 pub(super) const fn blocks_progress(&self) -> bool {
95 self.pending.is_some()
96 }
97
98 pub(super) fn has_pending_anchor_at_or_after(&self, round: Round) -> bool {
100 matches!(&self.pending, Some(pending) if pending.round() >= round)
101 }
102
103 pub(super) fn matches_pending_anchor(&self, commitment: C) -> bool {
105 matches!(&self.pending, Some(pending) if pending.proposal.payload == commitment)
106 }
107
108 pub(super) fn await_anchor(&mut self, finalization: Finalization<S, C>) {
110 self.pending = Some(finalization);
111 }
112
113 #[must_use]
115 pub(super) const fn take_pending_anchor(&mut self) -> Option<Finalization<S, C>> {
116 self.pending.take()
117 }
118
119 #[must_use]
124 pub(super) fn take_superseded_anchor(&mut self, round: Round) -> Option<Finalization<S, C>> {
125 self.pending.take_if(|pending| pending.round() <= round)
126 }
127
128 fn permits(&self, fetch: &Request<C>) -> bool {
130 if let Some(height) = self.height
131 && !fetch.above_height_floor(height)
132 {
133 return false;
134 }
135
136 fetch.above_round_floor(self.round)
137 }
138
139 pub(super) fn fetch_if_permitted<R>(
140 &self,
141 resolver: &mut R,
142 fetch: Request<C>,
143 ) -> FetchAdmission
144 where
145 R: Resolver<Key = Key<C>, Subscriber = Annotation>,
146 {
147 if !self.permits(&fetch) {
148 return FetchAdmission::Denied;
149 }
150 resolver.fetch(fetch);
151 FetchAdmission::Issued
152 }
153
154 pub(super) fn fetch_targeted_if_permitted<R>(
155 &self,
156 resolver: &mut R,
157 fetch: Request<C>,
158 targets: NonEmptyVec<R::PublicKey>,
159 ) -> FetchAdmission
160 where
161 R: TargetedResolver<Key = Key<C>, Subscriber = Annotation>,
162 {
163 if !self.permits(&fetch) {
164 return FetchAdmission::Denied;
165 }
166 resolver.fetch_targeted(fetch, targets);
167 FetchAdmission::Issued
168 }
169
170 pub(super) fn fetch_all_if_permitted<R>(
171 &self,
172 resolver: &mut R,
173 fetches: Vec<Request<C>>,
174 ) -> FetchAdmission
175 where
176 R: Resolver<Key = Key<C>, Subscriber = Annotation>,
177 {
178 let fetches = fetches
179 .into_iter()
180 .filter(|fetch| self.permits(fetch))
181 .collect::<Vec<_>>();
182 if fetches.is_empty() {
183 return FetchAdmission::Denied;
184 }
185 resolver.fetch_all(fetches);
186 FetchAdmission::Issued
187 }
188}
189
190#[must_use = "fetch admission must be handled explicitly"]
192pub(super) enum FetchAdmission {
193 Issued,
194 Denied,
195}
196
197impl FetchAdmission {
198 pub(super) const fn ignore(self) {}
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::{
205 marshal::resolver::handler::Finalized,
206 simplex::scheme::ed25519 as simplex_ed25519,
207 types::{Epoch, View},
208 };
209 use commonware_actor::Feedback;
210 use commonware_cryptography::{Signer as _, ed25519 as crypto_ed25519, sha256::Sha256};
211 use commonware_math::algebra::Random as _;
212 use commonware_resolver::Fetch;
213 use commonware_utils::sync::Mutex;
214 use std::sync::Arc;
215
216 type TestDigest = <Sha256 as commonware_cryptography::Hasher>::Digest;
217 type TestScheme = simplex_ed25519::Scheme;
218 type FetchRecord = Fetch<Key<TestDigest>, Annotation>;
219 type RecordedFetches = Arc<Mutex<Vec<FetchRecord>>>;
220 type RecordedTargets = Arc<Mutex<Vec<Key<TestDigest>>>>;
221
222 #[derive(Clone, Default)]
223 struct TestResolver {
224 fetches: RecordedFetches,
225 targeted: RecordedTargets,
226 }
227
228 impl TestResolver {
229 fn fetches(&self) -> Vec<FetchRecord> {
230 self.fetches.lock().clone()
231 }
232
233 fn targeted(&self) -> Vec<Key<TestDigest>> {
234 self.targeted.lock().clone()
235 }
236 }
237
238 impl Resolver for TestResolver {
239 type Key = Key<TestDigest>;
240 type Subscriber = Annotation;
241
242 fn fetch<F>(&mut self, fetch: F) -> Feedback
243 where
244 F: Into<Fetch<Self::Key, Self::Subscriber>> + Send,
245 {
246 self.fetches.lock().push(fetch.into());
247 Feedback::Ok
248 }
249
250 fn fetch_all<F>(&mut self, fetches: Vec<F>) -> Feedback
251 where
252 F: Into<Fetch<Self::Key, Self::Subscriber>> + Send,
253 {
254 self.fetches
255 .lock()
256 .extend(fetches.into_iter().map(Into::into));
257 Feedback::Ok
258 }
259
260 fn retain(
261 &mut self,
262 _predicate: impl Fn(&Self::Key, &Self::Subscriber) -> bool + Send + 'static,
263 ) -> Feedback {
264 Feedback::Ok
265 }
266 }
267
268 impl TargetedResolver for TestResolver {
269 type PublicKey = crypto_ed25519::PublicKey;
270
271 fn fetch_targeted(
272 &mut self,
273 fetch: impl Into<Fetch<Self::Key, Self::Subscriber>> + Send,
274 _targets: NonEmptyVec<Self::PublicKey>,
275 ) -> Feedback {
276 self.targeted.lock().push(fetch.into().key);
277 Feedback::Ok
278 }
279
280 fn fetch_all_targeted<F>(
281 &mut self,
282 fetches: Vec<(F, NonEmptyVec<Self::PublicKey>)>,
283 ) -> Feedback
284 where
285 F: Into<Fetch<Self::Key, Self::Subscriber>> + Send,
286 {
287 self.targeted
288 .lock()
289 .extend(fetches.into_iter().map(|(fetch, _)| fetch.into().key));
290 Feedback::Ok
291 }
292 }
293
294 fn round(view: u64) -> Round {
295 Round::new(Epoch::zero(), View::new(view))
296 }
297
298 fn digest(byte: u8) -> TestDigest {
299 Sha256::fill(byte)
300 }
301
302 fn floor() -> State<TestScheme, TestDigest> {
303 State::resolved(Some(Height::new(5)), round(5))
304 }
305
306 #[test]
307 fn fetch_if_permitted_applies_height_and_round_floors() {
308 let floor = floor();
309 let mut resolver = TestResolver::default();
310
311 assert!(matches!(
312 floor.fetch_if_permitted(&mut resolver, Request::finalized(Height::new(5))),
313 FetchAdmission::Denied
314 ));
315 assert!(matches!(
316 floor.fetch_if_permitted(
317 &mut resolver,
318 Request::finalized_block_by_height(digest(1), Height::new(4)),
319 ),
320 FetchAdmission::Denied
321 ));
322 assert!(matches!(
323 floor.fetch_if_permitted(&mut resolver, Request::notarized(round(5))),
324 FetchAdmission::Denied
325 ));
326 assert!(resolver.fetches().is_empty());
327
328 assert!(matches!(
329 floor.fetch_if_permitted(&mut resolver, Request::finalized(Height::new(6))),
330 FetchAdmission::Issued
331 ));
332 assert!(matches!(
333 floor.fetch_if_permitted(&mut resolver, Request::notarized(round(6))),
334 FetchAdmission::Issued
335 ));
336
337 let fetches = resolver.fetches();
338 assert_eq!(fetches.len(), 2);
339 assert!(matches!(
340 fetches[0],
341 Fetch {
342 key: Key::Finalized {
343 height
344 },
345 subscriber: Annotation::Finalized(Finalized::ByHeight {
346 height: subscriber_height
347 }),
348 ..
349 } if height == Height::new(6) && subscriber_height == Height::new(6)
350 ));
351 assert!(matches!(
352 fetches[1],
353 Fetch {
354 key: Key::Notarized {
355 round: request_round
356 },
357 subscriber: Annotation::Notarization {
358 round: subscriber_round
359 },
360 ..
361 } if request_round == round(6) && subscriber_round == round(6)
362 ));
363 }
364
365 #[test]
366 fn fetch_targeted_if_permitted_returns_denied_without_fetching() {
367 let floor = floor();
368 let mut resolver = TestResolver::default();
369 let mut rng = commonware_utils::test_rng();
370 let target = crypto_ed25519::PrivateKey::random(&mut rng).public_key();
371
372 assert!(matches!(
373 floor.fetch_targeted_if_permitted(
374 &mut resolver,
375 Request::finalized(Height::new(5)),
376 NonEmptyVec::new(target.clone()),
377 ),
378 FetchAdmission::Denied
379 ));
380 assert!(resolver.targeted().is_empty());
381
382 assert!(matches!(
383 floor.fetch_targeted_if_permitted(
384 &mut resolver,
385 Request::finalized(Height::new(6)),
386 NonEmptyVec::new(target),
387 ),
388 FetchAdmission::Issued
389 ));
390 assert_eq!(
391 resolver.targeted(),
392 vec![Key::Finalized {
393 height: Height::new(6)
394 }]
395 );
396 }
397
398 #[test]
399 fn fetch_all_if_permitted_filters_denied_requests() {
400 let floor = floor();
401 let mut resolver = TestResolver::default();
402
403 assert!(matches!(
404 floor.fetch_all_if_permitted(
405 &mut resolver,
406 vec![
407 Request::finalized(Height::new(5)),
408 Request::finalized(Height::new(6)),
409 Request::notarized(round(5)),
410 Request::notarized(round(6)),
411 ],
412 ),
413 FetchAdmission::Issued
414 ));
415
416 let fetches = resolver.fetches();
417 assert_eq!(fetches.len(), 2);
418 assert!(matches!(fetches[0].key, Key::Finalized { height } if height == Height::new(6)));
419 assert!(
420 matches!(fetches[1].key, Key::Notarized { round: request_round } if request_round == round(6))
421 );
422
423 let mut resolver = TestResolver::default();
424 assert!(matches!(
425 floor.fetch_all_if_permitted(
426 &mut resolver,
427 vec![
428 Request::finalized(Height::new(5)),
429 Request::notarized(round(5)),
430 ],
431 ),
432 FetchAdmission::Denied
433 ));
434 assert!(resolver.fetches().is_empty());
435 }
436
437 #[test]
438 fn fetch_if_permitted_without_height_floor_allows_genesis_height() {
439 let floor = State::<TestScheme, TestDigest>::resolved(None, round(5));
440 let mut resolver = TestResolver::default();
441
442 assert!(matches!(
443 floor.fetch_if_permitted(&mut resolver, Request::finalized(Height::zero())),
444 FetchAdmission::Issued
445 ));
446
447 let fetches = resolver.fetches();
448 assert_eq!(fetches.len(), 1);
449 assert!(matches!(
450 fetches[0],
451 Fetch {
452 key: Key::Finalized {
453 height
454 },
455 subscriber: Annotation::Finalized(Finalized::ByHeight {
456 height: subscriber_height
457 }),
458 ..
459 } if height == Height::zero() && subscriber_height == Height::zero()
460 ));
461 }
462}