1use std::fmt;
5
6use linera_base::{
7 data_types::{Blob, BlobContent, BlockHeight},
8 identifiers::{BlobId, ChainId},
9};
10use linera_chain::types::ConfirmedBlockCertificate;
11
12use crate::{client::requests_scheduler::cache::SubsumingKey, data_types::CompressedHeights};
13
14#[derive(Clone, PartialEq, Eq, Hash)]
18pub enum RequestKey {
19 Certificates {
21 chain_id: ChainId,
22 heights: Vec<BlockHeight>,
23 },
24 Blob(BlobId),
26 PendingBlob { chain_id: ChainId, blob_id: BlobId },
28 CertificateForBlob(BlobId),
30}
31
32impl fmt::Debug for RequestKey {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 RequestKey::Certificates { chain_id, heights } => f
36 .debug_struct("Certificates")
37 .field("chain_id", chain_id)
38 .field("heights", &CompressedHeights(heights))
39 .finish(),
40 RequestKey::Blob(blob_id) => f.debug_tuple("Blob").field(blob_id).finish(),
41 RequestKey::PendingBlob { chain_id, blob_id } => f
42 .debug_struct("PendingBlob")
43 .field("chain_id", chain_id)
44 .field("blob_id", blob_id)
45 .finish(),
46 RequestKey::CertificateForBlob(blob_id) => {
47 f.debug_tuple("CertificateForBlob").field(blob_id).finish()
48 }
49 }
50 }
51}
52
53impl RequestKey {
54 pub(super) fn chain_id(&self) -> Option<ChainId> {
56 match self {
57 RequestKey::Certificates { chain_id, .. } => Some(*chain_id),
58 RequestKey::PendingBlob { chain_id, .. } => Some(*chain_id),
59 _ => None,
60 }
61 }
62
63 fn heights(&self) -> Option<Vec<BlockHeight>> {
72 match self {
73 RequestKey::Certificates { heights, .. } => Some(heights.clone()),
74 _ => None,
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
81pub enum RequestResult {
82 Certificates(Vec<ConfirmedBlockCertificate>),
83 Blob(Option<Blob>),
84 BlobContent(BlobContent),
85 Certificate(Box<ConfirmedBlockCertificate>),
86}
87
88pub trait Cacheable: TryFrom<RequestResult> + Into<RequestResult> {}
91impl<T> Cacheable for T where T: TryFrom<RequestResult> + Into<RequestResult> {}
92
93impl From<Option<Blob>> for RequestResult {
94 fn from(blob: Option<Blob>) -> Self {
95 RequestResult::Blob(blob)
96 }
97}
98
99impl From<Vec<ConfirmedBlockCertificate>> for RequestResult {
100 fn from(certs: Vec<ConfirmedBlockCertificate>) -> Self {
101 RequestResult::Certificates(certs)
102 }
103}
104
105impl From<BlobContent> for RequestResult {
106 fn from(content: BlobContent) -> Self {
107 RequestResult::BlobContent(content)
108 }
109}
110
111impl From<ConfirmedBlockCertificate> for RequestResult {
112 fn from(cert: ConfirmedBlockCertificate) -> Self {
113 RequestResult::Certificate(Box::new(cert))
114 }
115}
116
117impl TryFrom<RequestResult> for Option<Blob> {
118 type Error = ();
119
120 fn try_from(result: RequestResult) -> Result<Self, Self::Error> {
121 match result {
122 RequestResult::Blob(blob) => Ok(blob),
123 _ => Err(()),
124 }
125 }
126}
127
128impl TryFrom<RequestResult> for Vec<ConfirmedBlockCertificate> {
129 type Error = ();
130
131 fn try_from(result: RequestResult) -> Result<Self, Self::Error> {
132 match result {
133 RequestResult::Certificates(certs) => Ok(certs),
134 _ => Err(()),
135 }
136 }
137}
138
139impl TryFrom<RequestResult> for BlobContent {
140 type Error = ();
141
142 fn try_from(result: RequestResult) -> Result<Self, Self::Error> {
143 match result {
144 RequestResult::BlobContent(content) => Ok(content),
145 _ => Err(()),
146 }
147 }
148}
149
150impl TryFrom<RequestResult> for ConfirmedBlockCertificate {
151 type Error = ();
152
153 fn try_from(result: RequestResult) -> Result<Self, Self::Error> {
154 match result {
155 RequestResult::Certificate(cert) => Ok(*cert),
156 _ => Err(()),
157 }
158 }
159}
160
161impl SubsumingKey<RequestResult> for super::request::RequestKey {
162 fn subsumes(&self, other: &Self) -> bool {
163 if self.chain_id() != other.chain_id() {
165 return false;
166 }
167
168 let (in_flight_req_heights, new_req_heights) = match (self.heights(), other.heights()) {
169 (Some(range1), Some(range2)) => (range1, range2),
170 _ => return false, };
172
173 let mut in_flight_req_heights_iter = in_flight_req_heights.into_iter();
174
175 for new_height in new_req_heights {
176 if !in_flight_req_heights_iter.any(|h| h == new_height) {
177 return false; }
179 }
180 true
181 }
182
183 fn try_extract_result(
184 &self,
185 in_flight_request: &RequestKey,
186 result: &RequestResult,
187 ) -> Option<RequestResult> {
188 let certificates = match result {
190 RequestResult::Certificates(certs) => certs,
191 _ => return None,
192 };
193
194 if !in_flight_request.subsumes(self) {
195 return None; }
197
198 let mut requested_heights = self.heights()?;
199 if requested_heights.is_empty() {
200 return Some(RequestResult::Certificates(vec![])); }
202 let mut certificates_iter = certificates.iter();
203 let mut collected = vec![];
204 while let Some(height) = requested_heights.first() {
205 collected.push(
207 certificates_iter
208 .find(|cert| &cert.value().height() == height)?
209 .clone(),
210 );
211 requested_heights.remove(0);
212 }
213
214 Some(RequestResult::Certificates(collected))
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use linera_base::{crypto::CryptoHash, data_types::BlockHeight, identifiers::ChainId};
221
222 use super::{RequestKey, SubsumingKey};
223
224 #[test]
225 fn test_subsumes_complete_containment() {
226 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
227 let large = RequestKey::Certificates {
228 chain_id,
229 heights: vec![BlockHeight(11), BlockHeight(12), BlockHeight(13)],
230 };
231 let small = RequestKey::Certificates {
232 chain_id,
233 heights: vec![BlockHeight(12)],
234 };
235 assert!(large.subsumes(&small));
236 assert!(!small.subsumes(&large));
237 }
238
239 #[test]
240 fn test_subsumes_partial_containment() {
241 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
242 let req1 = RequestKey::Certificates {
243 chain_id,
244 heights: vec![BlockHeight(12), BlockHeight(13)],
245 };
246 let req2 = RequestKey::Certificates {
247 chain_id,
248 heights: vec![BlockHeight(12), BlockHeight(14)],
249 };
250 assert!(!req1.subsumes(&req2));
251 assert!(!req2.subsumes(&req1));
252 }
253
254 #[test]
255 fn test_subsumes_different_chains() {
256 let chain1 = ChainId(CryptoHash::test_hash("chain1"));
257 let chain2 = ChainId(CryptoHash::test_hash("chain2"));
258 let req1 = RequestKey::Certificates {
259 chain_id: chain1,
260 heights: vec![BlockHeight(12)],
261 };
262 let req2 = RequestKey::Certificates {
263 chain_id: chain2,
264 heights: vec![BlockHeight(12)],
265 };
266 assert!(!req1.subsumes(&req2));
267 }
268
269 fn make_test_cert(
271 height: u64,
272 chain_id: ChainId,
273 ) -> linera_chain::types::ConfirmedBlockCertificate {
274 use linera_base::{
275 crypto::ValidatorKeypair,
276 data_types::{Round, Timestamp},
277 };
278 use linera_chain::{
279 block::ConfirmedBlock,
280 data_types::{BlockExecutionOutcome, LiteValue, LiteVote},
281 justification::JustificationChain,
282 test::{make_first_block, BlockTestExt, VoteTestExt},
283 types::ConfirmedBlockCertificate,
284 };
285
286 let keypair = ValidatorKeypair::generate();
287 let mut proposed_block = make_first_block(chain_id).with_timestamp(Timestamp::from(height));
288
289 proposed_block.height = BlockHeight(height);
291
292 let block = BlockExecutionOutcome::default().with(proposed_block);
294
295 let confirmed_block = ConfirmedBlock::new(block);
297
298 let lite_vote = LiteVote::new(
300 LiteValue::new(&confirmed_block),
301 Round::MultiLeader(0),
302 &keypair.secret_key,
303 );
304
305 let vote = lite_vote.with_value(confirmed_block).unwrap();
307
308 let quorum = vote.into_certificate(keypair.secret_key.public());
310 ConfirmedBlockCertificate::from_parts(quorum, JustificationChain::default())
311 }
312
313 #[test]
314 fn test_try_extract_result_non_certificate_result() {
315 use super::RequestResult;
316
317 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
318 let req1 = RequestKey::Certificates {
319 chain_id,
320 heights: vec![BlockHeight(12)],
321 };
322 let req2 = RequestKey::Certificates {
323 chain_id,
324 heights: vec![BlockHeight(12)],
325 };
326
327 let blob_result = RequestResult::Blob(None);
329 assert!(req1.try_extract_result(&req2, &blob_result).is_none());
330 }
331
332 #[test]
333 fn test_try_extract_result_empty_request_range() {
334 use super::RequestResult;
335
336 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
337 let req1 = RequestKey::Certificates {
338 chain_id,
339 heights: vec![],
340 };
341 let req2 = RequestKey::Certificates {
342 chain_id,
343 heights: vec![BlockHeight(10)],
344 };
345
346 let certs = vec![make_test_cert(10, chain_id)];
347 let result = RequestResult::Certificates(certs);
348
349 match req1.try_extract_result(&req2, &result) {
351 Some(RequestResult::Certificates(extracted_certs)) => {
352 assert!(extracted_certs.is_empty());
353 }
354 _ => panic!("Expected Some empty Certificates result"),
355 }
356 }
357
358 #[test]
359 fn test_try_extract_result_empty_result_range() {
360 use super::RequestResult;
361
362 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
363 let req1 = RequestKey::Certificates {
364 chain_id,
365 heights: vec![BlockHeight(12)],
366 };
367 let req2 = RequestKey::Certificates {
368 chain_id,
369 heights: vec![BlockHeight(12)],
370 };
371
372 let result = RequestResult::Certificates(vec![]); assert!(req1.try_extract_result(&req2, &result).is_none());
376 }
377
378 #[test]
379 fn test_try_extract_result_non_overlapping_ranges() {
380 use super::RequestResult;
381
382 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
383 let new_req = RequestKey::Certificates {
384 chain_id,
385 heights: vec![BlockHeight(10)],
386 };
387 let in_flight_req = RequestKey::Certificates {
388 chain_id,
389 heights: vec![BlockHeight(11)],
390 };
391
392 let certs = vec![make_test_cert(11, chain_id)];
394 let result = RequestResult::Certificates(certs);
395
396 assert!(new_req
398 .try_extract_result(&in_flight_req, &result)
399 .is_none());
400 }
401
402 #[test]
403 fn test_try_extract_result_partial_overlap_missing_start() {
404 use super::RequestResult;
405
406 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
407 let req1 = RequestKey::Certificates {
408 chain_id,
409 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
410 };
411 let req2 = RequestKey::Certificates {
412 chain_id,
413 heights: vec![BlockHeight(11), BlockHeight(12)],
414 };
415
416 let certs = vec![make_test_cert(11, chain_id), make_test_cert(12, chain_id)];
418 let result = RequestResult::Certificates(certs);
419
420 assert!(req1.try_extract_result(&req2, &result).is_none());
422 }
423
424 #[test]
425 fn test_try_extract_result_partial_overlap_missing_end() {
426 use super::RequestResult;
427
428 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
429 let req1 = RequestKey::Certificates {
430 chain_id,
431 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
432 };
433 let req2 = RequestKey::Certificates {
434 chain_id,
435 heights: vec![BlockHeight(10), BlockHeight(11)],
436 };
437
438 let certs = vec![make_test_cert(10, chain_id), make_test_cert(11, chain_id)];
440 let result = RequestResult::Certificates(certs);
441
442 assert!(req1.try_extract_result(&req2, &result).is_none());
444 }
445
446 #[test]
447 fn test_try_extract_result_partial_overlap_missing_middle() {
448 use super::RequestResult;
449
450 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
451 let new_req = RequestKey::Certificates {
452 chain_id,
453 heights: vec![BlockHeight(10), BlockHeight(12), BlockHeight(13)],
454 };
455 let in_flight_req = RequestKey::Certificates {
456 chain_id,
457 heights: vec![
458 BlockHeight(10),
459 BlockHeight(12),
460 BlockHeight(13),
461 BlockHeight(14),
462 ],
463 };
464
465 let certs = vec![
466 make_test_cert(10, chain_id),
467 make_test_cert(13, chain_id),
468 make_test_cert(14, chain_id),
469 ];
470 let result = RequestResult::Certificates(certs);
471
472 assert!(new_req
473 .try_extract_result(&in_flight_req, &result)
474 .is_none());
475 assert!(in_flight_req
476 .try_extract_result(&new_req, &result)
477 .is_none());
478 }
479
480 #[test]
481 fn test_try_extract_result_exact_match() {
482 use super::RequestResult;
483
484 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
485 let req1 = RequestKey::Certificates {
486 chain_id,
487 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
488 }; let req2 = RequestKey::Certificates {
490 chain_id,
491 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
492 };
493
494 let certs = vec![
495 make_test_cert(10, chain_id),
496 make_test_cert(11, chain_id),
497 make_test_cert(12, chain_id),
498 ];
499 let result = RequestResult::Certificates(certs.clone());
500
501 let extracted = req1.try_extract_result(&req2, &result);
503 assert!(extracted.is_some());
504 match extracted.unwrap() {
505 RequestResult::Certificates(extracted_certs) => {
506 assert_eq!(extracted_certs, certs);
507 }
508 _ => panic!("Expected Certificates result"),
509 }
510 }
511
512 #[test]
513 fn test_try_extract_result_superset_extraction() {
514 use super::RequestResult;
515
516 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
517 let req1 = RequestKey::Certificates {
518 chain_id,
519 heights: vec![BlockHeight(12), BlockHeight(13)],
520 };
521 let req2 = RequestKey::Certificates {
522 chain_id,
523 heights: vec![BlockHeight(12), BlockHeight(13)],
524 };
525
526 let certs = vec![
528 make_test_cert(10, chain_id),
529 make_test_cert(11, chain_id),
530 make_test_cert(12, chain_id),
531 make_test_cert(13, chain_id),
532 make_test_cert(14, chain_id),
533 ];
534 let result = RequestResult::Certificates(certs);
535
536 let extracted = req1.try_extract_result(&req2, &result);
538 assert!(extracted.is_some());
539 match extracted.unwrap() {
540 RequestResult::Certificates(extracted_certs) => {
541 assert_eq!(extracted_certs.len(), 2);
542 assert_eq!(extracted_certs[0].value().height(), BlockHeight(12));
543 assert_eq!(extracted_certs[1].value().height(), BlockHeight(13));
544 }
545 _ => panic!("Expected Certificates result"),
546 }
547 }
548
549 #[test]
550 fn test_try_extract_result_single_height() {
551 use super::RequestResult;
552
553 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
554 let req1 = RequestKey::Certificates {
555 chain_id,
556 heights: vec![BlockHeight(15)],
557 }; let req2 = RequestKey::Certificates {
559 chain_id,
560 heights: vec![BlockHeight(10), BlockHeight(15), BlockHeight(20)],
561 };
562
563 let certs = vec![
564 make_test_cert(10, chain_id),
565 make_test_cert(15, chain_id),
566 make_test_cert(20, chain_id),
567 ];
568 let result = RequestResult::Certificates(certs);
569
570 let extracted = req1.try_extract_result(&req2, &result);
572 assert!(extracted.is_some());
573 match extracted.unwrap() {
574 RequestResult::Certificates(extracted_certs) => {
575 assert_eq!(extracted_certs.len(), 1);
576 assert_eq!(extracted_certs[0].value().height(), BlockHeight(15));
577 }
578 _ => panic!("Expected Certificates result"),
579 }
580 }
581
582 #[test]
583 fn test_try_extract_result_different_chains() {
584 use super::RequestResult;
585
586 let chain1 = ChainId(CryptoHash::test_hash("chain1"));
587 let chain2 = ChainId(CryptoHash::test_hash("chain2"));
588 let req1 = RequestKey::Certificates {
589 chain_id: chain1,
590 heights: vec![BlockHeight(12)],
591 };
592 let req2 = RequestKey::Certificates {
593 chain_id: chain2,
594 heights: vec![BlockHeight(12)],
595 };
596
597 let certs = vec![make_test_cert(12, chain1)];
598 let result = RequestResult::Certificates(certs);
599
600 assert!(req1.try_extract_result(&req2, &result).is_none());
602 }
603}