mithril_signer/services/signature_publisher/
retrier.rs1use std::{sync::Arc, time::Duration};
2
3use mithril_common::{
4 StdResult,
5 entities::{ProtocolMessage, SignedEntityType, SingleSignature},
6};
7
8use super::SignaturePublisher;
9
10#[derive(Debug, PartialEq, Clone)]
12pub struct SignaturePublishRetryPolicy {
13 pub attempts: u8,
15 pub delay_between_attempts: Duration,
17}
18
19impl SignaturePublishRetryPolicy {
20 pub fn never() -> Self {
22 Self {
23 attempts: 1,
24 delay_between_attempts: Duration::from_secs(0),
25 }
26 }
27}
28
29impl Default for SignaturePublishRetryPolicy {
30 fn default() -> Self {
32 Self {
33 attempts: 3,
34 delay_between_attempts: Duration::from_secs(5),
35 }
36 }
37}
38
39pub struct SignaturePublisherRetrier {
41 publisher: Arc<dyn SignaturePublisher>,
42 retry_policy: SignaturePublishRetryPolicy,
43}
44
45impl SignaturePublisherRetrier {
46 pub fn new(
48 publisher: Arc<dyn SignaturePublisher>,
49 retry_policy: SignaturePublishRetryPolicy,
50 ) -> Self {
51 Self {
52 publisher,
53 retry_policy,
54 }
55 }
56}
57
58#[async_trait::async_trait]
59impl SignaturePublisher for SignaturePublisherRetrier {
60 async fn publish(
61 &self,
62 signed_entity_type: &SignedEntityType,
63 signature: &SingleSignature,
64 protocol_message: &ProtocolMessage,
65 ) -> StdResult<()> {
66 let mut nb_attempts = 0;
67 loop {
68 nb_attempts += 1;
69
70 match self
71 .publisher
72 .publish(signed_entity_type, signature, protocol_message)
73 .await
74 {
75 Ok(_) => return Ok(()),
76 Err(e) if nb_attempts >= self.retry_policy.attempts => {
77 return Err(anyhow::anyhow!(e)
78 .context(format!("Publish failed after {nb_attempts} attempts")));
79 }
80 _ => tokio::time::sleep(self.retry_policy.delay_between_attempts).await,
81 }
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use mithril_common::{entities::Epoch, test_utils::fake_data};
89
90 use crate::services::MockSignaturePublisher;
91
92 use super::*;
93
94 #[tokio::test]
95 async fn should_call_publish_once_when_no_retry_policy() {
96 let retry_policy = SignaturePublishRetryPolicy::never();
97
98 let mut publisher = MockSignaturePublisher::new();
99 publisher.expect_publish().once().returning(|_, _, _| Ok(()));
100
101 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
102
103 retrier
104 .publish(
105 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
106 &fake_data::single_signature(vec![1]),
107 &ProtocolMessage::default(),
108 )
109 .await
110 .unwrap();
111 }
112
113 #[tokio::test]
114 async fn should_not_retry_when_publish_fails_and_retry_policy_is_never() {
115 let retry_policy = SignaturePublishRetryPolicy::never();
116
117 let mut publisher = MockSignaturePublisher::new();
118 publisher
119 .expect_publish()
120 .once()
121 .returning(|_, _, _| Err(anyhow::anyhow!("error while publishing")));
122
123 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
124
125 retrier
126 .publish(
127 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
128 &fake_data::single_signature(vec![1]),
129 &ProtocolMessage::default(),
130 )
131 .await
132 .expect_err("An error should be returned");
133 }
134
135 #[tokio::test]
136 async fn should_retry_once_and_succeed_on_second_attempt() {
137 let retry_policy = SignaturePublishRetryPolicy {
138 attempts: 2,
139 delay_between_attempts: Duration::from_secs(0),
140 };
141
142 let mut publisher = MockSignaturePublisher::new();
143 publisher
144 .expect_publish()
145 .times(1)
146 .return_once(|_, _, _| Err(anyhow::anyhow!("error")));
147 publisher.expect_publish().times(1).return_once(|_, _, _| Ok(()));
148
149 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
150
151 retrier
152 .publish(
153 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
154 &fake_data::single_signature(vec![1]),
155 &ProtocolMessage::default(),
156 )
157 .await
158 .unwrap();
159 }
160
161 #[tokio::test]
162 async fn should_retry_and_return_error_after_max_attempts() {
163 let retry_policy = SignaturePublishRetryPolicy {
164 attempts: 2,
165 delay_between_attempts: Duration::from_secs(0),
166 };
167
168 let mut publisher = MockSignaturePublisher::new();
169 publisher
170 .expect_publish()
171 .times(2)
172 .returning(|_, _, _| Err(anyhow::anyhow!("error")));
173
174 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
175
176 retrier
177 .publish(
178 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
179 &fake_data::single_signature(vec![1]),
180 &ProtocolMessage::default(),
181 )
182 .await
183 .expect_err("An error should be returned after max attempts");
184 }
185
186 #[tokio::test]
187 async fn should_wait_between_retries_according_to_policy() {
188 let delay_between_attempts = Duration::from_millis(50);
189 let retry_policy = SignaturePublishRetryPolicy {
190 attempts: 2,
191 delay_between_attempts,
192 };
193
194 let mut publisher = MockSignaturePublisher::new();
195 publisher
196 .expect_publish()
197 .once()
198 .return_once(|_, _, _| Err(anyhow::anyhow!("error")));
199 publisher.expect_publish().once().return_once(|_, _, _| Ok(()));
200
201 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
202
203 let start_time = std::time::Instant::now();
204 retrier
205 .publish(
206 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
207 &fake_data::single_signature(vec![1]),
208 &ProtocolMessage::default(),
209 )
210 .await
211 .unwrap();
212
213 let elapsed_time = start_time.elapsed();
214 assert!(
215 elapsed_time >= delay_between_attempts,
216 "Expected at least {delay_between_attempts:?} time elapsed, but got {elapsed_time:?}"
217 );
218 assert!(
219 elapsed_time < delay_between_attempts * 2,
220 "Expected less than {:?} time elapsed, but got {:?}",
221 delay_between_attempts * 2,
222 elapsed_time
223 );
224 }
225}