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(e.context(format!("Publish failed after {nb_attempts} attempts")));
78 }
79 _ => tokio::time::sleep(self.retry_policy.delay_between_attempts).await,
80 }
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use mithril_common::{entities::Epoch, test::double::fake_data};
88
89 use crate::services::MockSignaturePublisher;
90
91 use super::*;
92
93 #[tokio::test]
94 async fn should_call_publish_once_when_no_retry_policy() {
95 let retry_policy = SignaturePublishRetryPolicy::never();
96
97 let mut publisher = MockSignaturePublisher::new();
98 publisher.expect_publish().once().returning(|_, _, _| Ok(()));
99
100 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
101
102 retrier
103 .publish(
104 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
105 &fake_data::single_signature(vec![1]),
106 &ProtocolMessage::default(),
107 )
108 .await
109 .unwrap();
110 }
111
112 #[tokio::test]
113 async fn should_not_retry_when_publish_fails_and_retry_policy_is_never() {
114 let retry_policy = SignaturePublishRetryPolicy::never();
115
116 let mut publisher = MockSignaturePublisher::new();
117 publisher
118 .expect_publish()
119 .once()
120 .returning(|_, _, _| Err(anyhow::anyhow!("error while publishing")));
121
122 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
123
124 retrier
125 .publish(
126 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
127 &fake_data::single_signature(vec![1]),
128 &ProtocolMessage::default(),
129 )
130 .await
131 .expect_err("An error should be returned");
132 }
133
134 #[tokio::test]
135 async fn should_retry_once_and_succeed_on_second_attempt() {
136 let retry_policy = SignaturePublishRetryPolicy {
137 attempts: 2,
138 delay_between_attempts: Duration::from_secs(0),
139 };
140
141 let mut publisher = MockSignaturePublisher::new();
142 publisher
143 .expect_publish()
144 .times(1)
145 .return_once(|_, _, _| Err(anyhow::anyhow!("error")));
146 publisher.expect_publish().times(1).return_once(|_, _, _| Ok(()));
147
148 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
149
150 retrier
151 .publish(
152 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
153 &fake_data::single_signature(vec![1]),
154 &ProtocolMessage::default(),
155 )
156 .await
157 .unwrap();
158 }
159
160 #[tokio::test]
161 async fn should_retry_and_return_error_after_max_attempts() {
162 let retry_policy = SignaturePublishRetryPolicy {
163 attempts: 2,
164 delay_between_attempts: Duration::from_secs(0),
165 };
166
167 let mut publisher = MockSignaturePublisher::new();
168 publisher
169 .expect_publish()
170 .times(2)
171 .returning(|_, _, _| Err(anyhow::anyhow!("error")));
172
173 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
174
175 retrier
176 .publish(
177 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
178 &fake_data::single_signature(vec![1]),
179 &ProtocolMessage::default(),
180 )
181 .await
182 .expect_err("An error should be returned after max attempts");
183 }
184
185 #[tokio::test]
186 async fn should_wait_between_retries_according_to_policy() {
187 let delay_between_attempts = Duration::from_millis(50);
188 let retry_policy = SignaturePublishRetryPolicy {
189 attempts: 2,
190 delay_between_attempts,
191 };
192
193 let mut publisher = MockSignaturePublisher::new();
194 publisher
195 .expect_publish()
196 .once()
197 .return_once(|_, _, _| Err(anyhow::anyhow!("error")));
198 publisher.expect_publish().once().return_once(|_, _, _| Ok(()));
199
200 let retrier = SignaturePublisherRetrier::new(Arc::new(publisher), retry_policy);
201
202 let start_time = std::time::Instant::now();
203 retrier
204 .publish(
205 &SignedEntityType::MithrilStakeDistribution(Epoch(1)),
206 &fake_data::single_signature(vec![1]),
207 &ProtocolMessage::default(),
208 )
209 .await
210 .unwrap();
211
212 let elapsed_time = start_time.elapsed();
213 assert!(
214 elapsed_time >= delay_between_attempts,
215 "Expected at least {delay_between_attempts:?} time elapsed, but got {elapsed_time:?}"
216 );
217 assert!(
218 elapsed_time < delay_between_attempts * 2,
219 "Expected less than {:?} time elapsed, but got {:?}",
220 delay_between_attempts * 2,
221 elapsed_time
222 );
223 }
224}