solana_core/repair/
outstanding_requests.rs1use {
2 crate::repair::request_response::RequestResponse,
3 lru::LruCache,
4 rand::{thread_rng, Rng},
5 solana_ledger::shred::Nonce,
6};
7
8pub const DEFAULT_REQUEST_EXPIRATION_MS: u64 = 60_000;
9
10pub struct OutstandingRequests<T> {
11 requests: LruCache<Nonce, RequestStatus<T>>,
12}
13
14impl<T, S: ?Sized> OutstandingRequests<T>
15where
16 T: RequestResponse<Response = S>,
17{
18 pub fn add_request(&mut self, request: T, now: u64) -> Nonce {
21 let num_expected_responses = request.num_expected_responses();
22 let nonce = thread_rng().gen_range(0..Nonce::MAX);
23 self.requests.put(
24 nonce,
25 RequestStatus {
26 expire_timestamp: now + DEFAULT_REQUEST_EXPIRATION_MS,
27 num_expected_responses,
28 request,
29 },
30 );
31 nonce
32 }
33
34 pub fn register_response<R>(
35 &mut self,
36 nonce: u32,
37 response: &S,
38 now: u64,
39 success_fn: impl Fn(&T) -> R,
41 ) -> Option<R> {
42 let (response, should_delete) = self
43 .requests
44 .get_mut(&nonce)
45 .map(|status| {
46 if status.num_expected_responses > 0
47 && now < status.expire_timestamp
48 && status.request.verify_response(response)
49 {
50 status.num_expected_responses -= 1;
51 (
52 Some(success_fn(&status.request)),
53 status.num_expected_responses == 0,
54 )
55 } else {
56 (None, true)
57 }
58 })
59 .unwrap_or((None, false));
60
61 if should_delete {
62 self.requests
63 .pop(&nonce)
64 .expect("Delete must delete existing object");
65 }
66
67 response
68 }
69}
70
71impl<T> Default for OutstandingRequests<T> {
72 fn default() -> Self {
73 Self {
74 requests: LruCache::new(16 * 1024),
75 }
76 }
77}
78
79pub struct RequestStatus<T> {
80 expire_timestamp: u64,
81 num_expected_responses: u32,
82 request: T,
83}
84
85#[cfg(test)]
86pub(crate) mod tests {
87 use {
88 super::*, crate::repair::serve_repair::ShredRepairType, solana_keypair::Keypair,
89 solana_ledger::shred::Shredder, solana_time_utils::timestamp,
90 };
91
92 #[test]
93 fn test_add_request() {
94 let repair_type = ShredRepairType::Orphan(9);
95 let mut outstanding_requests = OutstandingRequests::default();
96 let nonce = outstanding_requests.add_request(repair_type, timestamp());
97 let request_status = outstanding_requests.requests.get(&nonce).unwrap();
98 assert_eq!(request_status.request, repair_type);
99 assert_eq!(
100 request_status.num_expected_responses,
101 repair_type.num_expected_responses()
102 );
103 }
104
105 #[test]
106 fn test_timeout_expired_remove() {
107 let repair_type = ShredRepairType::Orphan(9);
108 let mut outstanding_requests = OutstandingRequests::default();
109 let nonce = outstanding_requests.add_request(repair_type, timestamp());
110 let keypair = Keypair::new();
111 let shred = Shredder::single_shred_for_tests(0, &keypair);
112
113 let expire_timestamp = outstanding_requests
114 .requests
115 .get(&nonce)
116 .unwrap()
117 .expire_timestamp;
118
119 assert!(outstanding_requests
120 .register_response(nonce, shred.payload(), expire_timestamp + 1, |_| ())
121 .is_none());
122 assert!(outstanding_requests.requests.get(&nonce).is_none());
123 }
124
125 #[test]
126 fn test_register_response() {
127 let repair_type = ShredRepairType::Orphan(9);
128 let mut outstanding_requests = OutstandingRequests::default();
129 let nonce = outstanding_requests.add_request(repair_type, timestamp());
130 let keypair = Keypair::new();
131 let shred = Shredder::single_shred_for_tests(0, &keypair);
132 let mut expire_timestamp = outstanding_requests
133 .requests
134 .get(&nonce)
135 .unwrap()
136 .expire_timestamp;
137 let mut num_expected_responses = outstanding_requests
138 .requests
139 .get(&nonce)
140 .unwrap()
141 .num_expected_responses;
142 assert!(num_expected_responses > 1);
143
144 assert!(outstanding_requests
146 .register_response(nonce, shred.payload(), expire_timestamp - 1, |_| ())
147 .is_some());
148 num_expected_responses -= 1;
149 assert_eq!(
150 outstanding_requests
151 .requests
152 .get(&nonce)
153 .unwrap()
154 .num_expected_responses,
155 num_expected_responses
156 );
157
158 assert!(outstanding_requests
160 .register_response(nonce + 1, shred.payload(), expire_timestamp - 1, |_| ())
161 .is_none());
162 assert!(outstanding_requests
163 .register_response(nonce + 1, shred.payload(), expire_timestamp, |_| ())
164 .is_none());
165 assert_eq!(
166 outstanding_requests
167 .requests
168 .get(&nonce)
169 .unwrap()
170 .num_expected_responses,
171 num_expected_responses
172 );
173
174 assert!(outstanding_requests
177 .register_response(nonce, shred.payload(), expire_timestamp, |_| ())
178 .is_none());
179 assert!(outstanding_requests.requests.get(&nonce).is_none());
180
181 let nonce = outstanding_requests.add_request(repair_type, timestamp());
183 expire_timestamp = outstanding_requests
184 .requests
185 .get(&nonce)
186 .unwrap()
187 .expire_timestamp;
188 num_expected_responses = outstanding_requests
189 .requests
190 .get(&nonce)
191 .unwrap()
192 .num_expected_responses;
193 assert!(num_expected_responses > 1);
194 for _ in 0..num_expected_responses {
195 assert!(outstanding_requests.requests.get(&nonce).is_some());
196 assert!(outstanding_requests
197 .register_response(nonce, shred.payload(), expire_timestamp - 1, |_| ())
198 .is_some());
199 }
200 assert!(outstanding_requests.requests.get(&nonce).is_none());
201 }
202}