1+ use std:: time:: Duration ;
12use aligned_sdk:: common:: types:: VerificationData ;
3+ use tracing:: { info, warn} ;
4+ use crate :: backend:: retry:: { retry_function, RetryError } ;
25
36#[ derive( Debug ) ]
47#[ allow( dead_code) ]
@@ -13,38 +16,92 @@ pub enum GetBatchProofsError {
1316// needed to make S3 bucket work
1417const DEFAULT_USER_AGENT : & str = "proof-aggregator/aligned-layer" ;
1518
16- pub async fn get_aligned_batch_from_s3 (
19+ // Retry parameters for S3 requests
20+ /// Initial delay before first retry attempt (in milliseconds)
21+ const RETRY_MIN_DELAY_MILLIS : u64 = 500 ;
22+ /// Exponential backoff multiplier for retry delays
23+ const RETRY_FACTOR : f32 = 2.0 ;
24+ /// Maximum number of retry attempts
25+ const RETRY_MAX_TIMES : usize = 5 ;
26+ /// Maximum delay between retry attempts (in seconds)
27+ const RETRY_MAX_DELAY_SECONDS : u64 = 10 ;
28+
29+ /// Timeout for Reqwest Client
30+ const REQWEST_TIMEOUT_SECONDS : Duration = Duration :: from_secs ( 60 ) ;
31+
32+ async fn get_aligned_batch_from_s3_retryable (
1733 url : String ,
18- ) -> Result < Vec < VerificationData > , GetBatchProofsError > {
34+ ) -> Result < Vec < VerificationData > , RetryError < GetBatchProofsError > > {
35+ info ! ( "Fetching batch from S3 URL: {}" , url) ;
1936 let client = reqwest:: Client :: builder ( )
2037 . user_agent ( DEFAULT_USER_AGENT )
38+ . timeout ( REQWEST_TIMEOUT_SECONDS )
2139 . build ( )
22- . map_err ( |e| GetBatchProofsError :: ReqwestClientFailed ( e. to_string ( ) ) ) ?;
40+ . map_err ( |e| RetryError :: Permanent ( GetBatchProofsError :: ReqwestClientFailed ( e. to_string ( ) ) ) ) ?;
2341
2442 let response = client
25- . get ( url)
43+ . get ( & url)
2644 . send ( )
2745 . await
28- . map_err ( |e| GetBatchProofsError :: FetchingS3Batch ( e. to_string ( ) ) ) ?;
46+ . map_err ( |e| {
47+ warn ! ( "Failed to send request to {}: {}" , url, e) ;
48+ RetryError :: Transient ( GetBatchProofsError :: FetchingS3Batch ( e. to_string ( ) ) )
49+ } ) ?;
50+
2951 if !response. status ( ) . is_success ( ) {
30- return Err ( GetBatchProofsError :: StatusFailed ( (
31- response. status ( ) . as_u16 ( ) ,
32- response
33- . status ( )
34- . canonical_reason ( )
35- . unwrap_or ( "" )
36- . to_string ( ) ,
37- ) ) ) ;
52+ let status_code = response. status ( ) . as_u16 ( ) ;
53+ let reason = response. status ( ) . canonical_reason ( ) . unwrap_or ( "" ) . to_string ( ) ;
54+
55+ // Determine if the error is retryable based on status code
56+ let error = GetBatchProofsError :: StatusFailed ( ( status_code, reason) ) ;
57+ return match status_code {
58+ // Client errors (4xx) are generally permanent, except for specific cases
59+ 400 ..=499 => match status_code {
60+ 408 | 429 => Err ( RetryError :: Transient ( error) ) , // Request Timeout, Too Many Requests
61+ _ => Err ( RetryError :: Permanent ( error) ) ,
62+ } ,
63+ // Server errors (5xx) are generally transient
64+ 500 ..=599 => Err ( RetryError :: Transient ( error) ) ,
65+ _ => Err ( RetryError :: Permanent ( error) ) ,
66+ } ;
3867 }
3968
4069 let bytes = response
4170 . bytes ( )
4271 . await
43- . map_err ( |e| GetBatchProofsError :: EmptyBody ( e. to_string ( ) ) ) ?;
72+ . map_err ( |e| {
73+ warn ! ( "Failed to read response body from {}: {}" , url, e) ;
74+ RetryError :: Transient ( GetBatchProofsError :: EmptyBody ( e. to_string ( ) ) )
75+ } ) ?;
4476 let bytes: & [ u8 ] = bytes. iter ( ) . as_slice ( ) ;
4577
4678 let data: Vec < VerificationData > = ciborium:: from_reader ( bytes)
47- . map_err ( |e| GetBatchProofsError :: Deserialization ( e. to_string ( ) ) ) ?;
79+ . map_err ( |e| {
80+ warn ! ( "Failed to deserialize batch data from {}: {}" , url, e) ;
81+ RetryError :: Permanent ( GetBatchProofsError :: Deserialization ( e. to_string ( ) ) )
82+ } ) ?;
4883
4984 Ok ( data)
5085}
86+
87+ /// Download batch from Storage Service using the provided URL.
88+ ///
89+ /// Retries on recoverable errors using exponential backoff up to `RETRY_MAX_TIMES` times:
90+ /// (0,5 secs - 1 secs - 2 secs - 4 secs - 8 secs).
91+ pub async fn get_aligned_batch_from_s3 (
92+ url : String ,
93+ ) -> Result < Vec < VerificationData > , GetBatchProofsError > {
94+ let url_clone = url. clone ( ) ;
95+ retry_function (
96+ move || {
97+ let url = url_clone. clone ( ) ;
98+ get_aligned_batch_from_s3_retryable ( url)
99+ } ,
100+ RETRY_MIN_DELAY_MILLIS ,
101+ RETRY_FACTOR ,
102+ RETRY_MAX_TIMES ,
103+ RETRY_MAX_DELAY_SECONDS ,
104+ )
105+ . await
106+ . map_err ( |retry_err| retry_err. inner ( ) )
107+ }
0 commit comments