1use ant_protocol::{
10 messages::{ChunkProof, Nonce},
11 PrettyPrintRecordKey, CLOSE_GROUP_SIZE,
12};
13use core::fmt::{self, Debug};
14use exponential_backoff::Backoff;
15use libp2p::{
16 kad::{Quorum, Record},
17 PeerId,
18};
19use std::{collections::HashSet, num::NonZeroUsize, time::Duration};
20
21use crate::close_group_majority;
22
23#[derive(Clone, Debug, Copy, Default)]
33pub enum RetryStrategy {
34 None,
36 Quick,
38 #[default]
40 Balanced,
41 Persistent,
43 N(NonZeroUsize),
45}
46
47impl RetryStrategy {
48 pub fn attempts(&self) -> usize {
49 match self {
50 RetryStrategy::None => 1,
51 RetryStrategy::Quick => 4,
52 RetryStrategy::Balanced => 6,
53 RetryStrategy::Persistent => 10,
54 RetryStrategy::N(x) => x.get(),
55 }
56 }
57
58 pub fn backoff(&self) -> Backoff {
59 let mut backoff = Backoff::new(
60 self.attempts() as u32,
61 Duration::from_secs(1), Some(Duration::from_secs(32)),
63 );
64 backoff.set_factor(2); backoff.set_jitter(0.2); backoff
67 }
68}
69
70impl fmt::Display for RetryStrategy {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{self:?}")
73 }
74}
75
76#[derive(Debug, Copy, Clone, PartialEq, Eq)]
78pub enum ResponseQuorum {
79 One,
80 Majority,
81 All,
82 N(NonZeroUsize),
83}
84
85impl std::str::FromStr for ResponseQuorum {
86 type Err = String;
87
88 fn from_str(s: &str) -> Result<Self, Self::Err> {
89 match s {
90 "one" => Ok(ResponseQuorum::One),
91 "majority" => Ok(ResponseQuorum::Majority),
92 "all" => Ok(ResponseQuorum::All),
93 _ => {
94 if let Ok(n) = s.parse::<usize>() {
95 let n = NonZeroUsize::new(n);
96 match n {
97 Some(n) => Ok(ResponseQuorum::N(n)),
98 None => Err("Quorum value must be greater than 0".to_string()),
99 }
100 } else {
101 Err("Invalid quorum value".to_string())
102 }
103 }
104 }
105 }
106}
107
108impl ResponseQuorum {
109 pub(crate) fn get_kad_quorum(&self) -> Quorum {
110 match self {
111 ResponseQuorum::One => Quorum::One,
112 ResponseQuorum::Majority => Quorum::Majority,
113 ResponseQuorum::All => Quorum::All,
114 ResponseQuorum::N(n) => Quorum::N(*n),
115 }
116 }
117
118 pub fn get_value(&self) -> usize {
120 match self {
121 ResponseQuorum::Majority => close_group_majority(),
122 ResponseQuorum::All => CLOSE_GROUP_SIZE,
123 ResponseQuorum::N(v) => v.get(),
124 ResponseQuorum::One => 1,
125 }
126 }
127}
128
129#[derive(Clone)]
131pub struct GetRecordCfg {
132 pub get_quorum: ResponseQuorum,
134 pub retry_strategy: RetryStrategy,
136 pub target_record: Option<Record>,
138 pub expected_holders: HashSet<PeerId>,
140}
141
142impl GetRecordCfg {
143 pub fn does_target_match(&self, record: &Record) -> bool {
144 if let Some(ref target_record) = self.target_record {
145 target_record == record
146 } else {
147 true
149 }
150 }
151}
152
153impl Debug for GetRecordCfg {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 let mut f = f.debug_struct("GetRecordCfg");
156 f.field("get_quorum", &self.get_quorum)
157 .field("retry_strategy", &self.retry_strategy);
158
159 match &self.target_record {
160 Some(record) => {
161 let pretty_key = PrettyPrintRecordKey::from(&record.key);
162 f.field("target_record", &pretty_key);
163 }
164 None => {
165 f.field("target_record", &"None");
166 }
167 };
168
169 f.field("expected_holders", &self.expected_holders).finish()
170 }
171}
172
173#[derive(Debug, Clone)]
175pub struct PutRecordCfg {
176 pub put_quorum: ResponseQuorum,
180 pub retry_strategy: RetryStrategy,
182 pub use_put_record_to: Option<Vec<PeerId>>,
185 pub verification: Option<(VerificationKind, GetRecordCfg)>,
187}
188
189#[derive(Debug, Clone)]
191pub enum VerificationKind {
192 Network,
194 Crdt,
196 ChunkProof {
198 expected_proof: ChunkProof,
199 nonce: Nonce,
200 },
201}
202
203#[test]
204fn verify_retry_strategy_intervals() {
205 let intervals = |strategy: RetryStrategy| -> Vec<u32> {
206 let mut backoff = strategy.backoff();
207 backoff.set_jitter(0.01); backoff
209 .into_iter()
210 .flatten()
211 .map(|duration| duration.as_secs_f64().round() as u32)
212 .collect()
213 };
214
215 assert_eq!(intervals(RetryStrategy::None), Vec::<u32>::new());
216 assert_eq!(intervals(RetryStrategy::Quick), vec![2, 4, 8]);
217 assert_eq!(intervals(RetryStrategy::Balanced), vec![2, 4, 8, 16, 32]);
218 assert_eq!(
219 intervals(RetryStrategy::Persistent),
220 vec![2, 4, 8, 16, 32, 32, 32, 32, 32]
221 );
222 assert_eq!(
223 intervals(RetryStrategy::N(NonZeroUsize::new(12).unwrap())),
224 vec![2, 4, 8, 16, 32, 32, 32, 32, 32, 32, 32]
225 );
226}