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 if let Some(cert) = certificates_iter.find(|cert| &cert.value().height() == height) {
207 collected.push(cert.clone());
208 requested_heights.remove(0);
209 } else {
210 return None; }
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 test::{make_first_block, BlockTestExt, VoteTestExt},
282 };
283
284 let keypair = ValidatorKeypair::generate();
285 let mut proposed_block = make_first_block(chain_id).with_timestamp(Timestamp::from(height));
286
287 proposed_block.height = BlockHeight(height);
289
290 let block = BlockExecutionOutcome::default().with(proposed_block);
292
293 let confirmed_block = ConfirmedBlock::new(block);
295
296 let lite_vote = LiteVote::new(
298 LiteValue::new(&confirmed_block),
299 Round::MultiLeader(0),
300 &keypair.secret_key,
301 );
302
303 let vote = lite_vote.with_value(confirmed_block).unwrap();
305
306 vote.into_certificate(keypair.secret_key.public())
308 }
309
310 #[test]
311 fn test_try_extract_result_non_certificate_result() {
312 use super::RequestResult;
313
314 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
315 let req1 = RequestKey::Certificates {
316 chain_id,
317 heights: vec![BlockHeight(12)],
318 };
319 let req2 = RequestKey::Certificates {
320 chain_id,
321 heights: vec![BlockHeight(12)],
322 };
323
324 let blob_result = RequestResult::Blob(None);
326 assert!(req1.try_extract_result(&req2, &blob_result).is_none());
327 }
328
329 #[test]
330 fn test_try_extract_result_empty_request_range() {
331 use super::RequestResult;
332
333 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
334 let req1 = RequestKey::Certificates {
335 chain_id,
336 heights: vec![],
337 };
338 let req2 = RequestKey::Certificates {
339 chain_id,
340 heights: vec![BlockHeight(10)],
341 };
342
343 let certs = vec![make_test_cert(10, chain_id)];
344 let result = RequestResult::Certificates(certs);
345
346 match req1.try_extract_result(&req2, &result) {
348 Some(RequestResult::Certificates(extracted_certs)) => {
349 assert!(extracted_certs.is_empty());
350 }
351 _ => panic!("Expected Some empty Certificates result"),
352 }
353 }
354
355 #[test]
356 fn test_try_extract_result_empty_result_range() {
357 use super::RequestResult;
358
359 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
360 let req1 = RequestKey::Certificates {
361 chain_id,
362 heights: vec![BlockHeight(12)],
363 };
364 let req2 = RequestKey::Certificates {
365 chain_id,
366 heights: vec![BlockHeight(12)],
367 };
368
369 let result = RequestResult::Certificates(vec![]); assert!(req1.try_extract_result(&req2, &result).is_none());
373 }
374
375 #[test]
376 fn test_try_extract_result_non_overlapping_ranges() {
377 use super::RequestResult;
378
379 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
380 let new_req = RequestKey::Certificates {
381 chain_id,
382 heights: vec![BlockHeight(10)],
383 };
384 let in_flight_req = RequestKey::Certificates {
385 chain_id,
386 heights: vec![BlockHeight(11)],
387 };
388
389 let certs = vec![make_test_cert(11, chain_id)];
391 let result = RequestResult::Certificates(certs);
392
393 assert!(new_req
395 .try_extract_result(&in_flight_req, &result)
396 .is_none());
397 }
398
399 #[test]
400 fn test_try_extract_result_partial_overlap_missing_start() {
401 use super::RequestResult;
402
403 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
404 let req1 = RequestKey::Certificates {
405 chain_id,
406 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
407 };
408 let req2 = RequestKey::Certificates {
409 chain_id,
410 heights: vec![BlockHeight(11), BlockHeight(12)],
411 };
412
413 let certs = vec![make_test_cert(11, chain_id), make_test_cert(12, chain_id)];
415 let result = RequestResult::Certificates(certs);
416
417 assert!(req1.try_extract_result(&req2, &result).is_none());
419 }
420
421 #[test]
422 fn test_try_extract_result_partial_overlap_missing_end() {
423 use super::RequestResult;
424
425 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
426 let req1 = RequestKey::Certificates {
427 chain_id,
428 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
429 };
430 let req2 = RequestKey::Certificates {
431 chain_id,
432 heights: vec![BlockHeight(10), BlockHeight(11)],
433 };
434
435 let certs = vec![make_test_cert(10, chain_id), make_test_cert(11, chain_id)];
437 let result = RequestResult::Certificates(certs);
438
439 assert!(req1.try_extract_result(&req2, &result).is_none());
441 }
442
443 #[test]
444 fn test_try_extract_result_partial_overlap_missing_middle() {
445 use super::RequestResult;
446
447 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
448 let new_req = RequestKey::Certificates {
449 chain_id,
450 heights: vec![BlockHeight(10), BlockHeight(12), BlockHeight(13)],
451 };
452 let in_flight_req = RequestKey::Certificates {
453 chain_id,
454 heights: vec![
455 BlockHeight(10),
456 BlockHeight(12),
457 BlockHeight(13),
458 BlockHeight(14),
459 ],
460 };
461
462 let certs = vec![
463 make_test_cert(10, chain_id),
464 make_test_cert(13, chain_id),
465 make_test_cert(14, chain_id),
466 ];
467 let result = RequestResult::Certificates(certs);
468
469 assert!(new_req
470 .try_extract_result(&in_flight_req, &result)
471 .is_none());
472 assert!(in_flight_req
473 .try_extract_result(&new_req, &result)
474 .is_none());
475 }
476
477 #[test]
478 fn test_try_extract_result_exact_match() {
479 use super::RequestResult;
480
481 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
482 let req1 = RequestKey::Certificates {
483 chain_id,
484 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
485 }; let req2 = RequestKey::Certificates {
487 chain_id,
488 heights: vec![BlockHeight(10), BlockHeight(11), BlockHeight(12)],
489 };
490
491 let certs = vec![
492 make_test_cert(10, chain_id),
493 make_test_cert(11, chain_id),
494 make_test_cert(12, chain_id),
495 ];
496 let result = RequestResult::Certificates(certs.clone());
497
498 let extracted = req1.try_extract_result(&req2, &result);
500 assert!(extracted.is_some());
501 match extracted.unwrap() {
502 RequestResult::Certificates(extracted_certs) => {
503 assert_eq!(extracted_certs, certs);
504 }
505 _ => panic!("Expected Certificates result"),
506 }
507 }
508
509 #[test]
510 fn test_try_extract_result_superset_extraction() {
511 use super::RequestResult;
512
513 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
514 let req1 = RequestKey::Certificates {
515 chain_id,
516 heights: vec![BlockHeight(12), BlockHeight(13)],
517 };
518 let req2 = RequestKey::Certificates {
519 chain_id,
520 heights: vec![BlockHeight(12), BlockHeight(13)],
521 };
522
523 let certs = vec![
525 make_test_cert(10, chain_id),
526 make_test_cert(11, chain_id),
527 make_test_cert(12, chain_id),
528 make_test_cert(13, chain_id),
529 make_test_cert(14, chain_id),
530 ];
531 let result = RequestResult::Certificates(certs);
532
533 let extracted = req1.try_extract_result(&req2, &result);
535 assert!(extracted.is_some());
536 match extracted.unwrap() {
537 RequestResult::Certificates(extracted_certs) => {
538 assert_eq!(extracted_certs.len(), 2);
539 assert_eq!(extracted_certs[0].value().height(), BlockHeight(12));
540 assert_eq!(extracted_certs[1].value().height(), BlockHeight(13));
541 }
542 _ => panic!("Expected Certificates result"),
543 }
544 }
545
546 #[test]
547 fn test_try_extract_result_single_height() {
548 use super::RequestResult;
549
550 let chain_id = ChainId(CryptoHash::test_hash("chain1"));
551 let req1 = RequestKey::Certificates {
552 chain_id,
553 heights: vec![BlockHeight(15)],
554 }; let req2 = RequestKey::Certificates {
556 chain_id,
557 heights: vec![BlockHeight(10), BlockHeight(15), BlockHeight(20)],
558 };
559
560 let certs = vec![
561 make_test_cert(10, chain_id),
562 make_test_cert(15, chain_id),
563 make_test_cert(20, chain_id),
564 ];
565 let result = RequestResult::Certificates(certs);
566
567 let extracted = req1.try_extract_result(&req2, &result);
569 assert!(extracted.is_some());
570 match extracted.unwrap() {
571 RequestResult::Certificates(extracted_certs) => {
572 assert_eq!(extracted_certs.len(), 1);
573 assert_eq!(extracted_certs[0].value().height(), BlockHeight(15));
574 }
575 _ => panic!("Expected Certificates result"),
576 }
577 }
578
579 #[test]
580 fn test_try_extract_result_different_chains() {
581 use super::RequestResult;
582
583 let chain1 = ChainId(CryptoHash::test_hash("chain1"));
584 let chain2 = ChainId(CryptoHash::test_hash("chain2"));
585 let req1 = RequestKey::Certificates {
586 chain_id: chain1,
587 heights: vec![BlockHeight(12)],
588 };
589 let req2 = RequestKey::Certificates {
590 chain_id: chain2,
591 heights: vec![BlockHeight(12)],
592 };
593
594 let certs = vec![make_test_cert(12, chain1)];
595 let result = RequestResult::Certificates(certs);
596
597 assert!(req1.try_extract_result(&req2, &result).is_none());
599 }
600}