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