@@ -11,12 +11,25 @@ use nillion_nucs::{
1111 } ,
1212 token:: { Did , ProofHash , TokenBody } ,
1313} ;
14- use serde:: { Deserialize , Serialize } ;
14+ use reqwest:: Response ;
15+ use serde:: { de:: DeserializeOwned , Deserialize , Serialize } ;
1516use serde_json:: json;
16- use std:: { iter, time:: Duration } ;
17+ use std:: { fmt:: Display , iter, time:: Duration } ;
18+ use tokio:: time:: sleep;
19+ use tracing:: warn;
1720
1821const TOKEN_REQUEST_EXPIRATION : Duration = Duration :: from_secs ( 60 ) ;
1922const REQUEST_TIMEOUT : Duration = Duration :: from_secs ( 30 ) ;
23+ const TX_RETRY_ERROR_CODE : & str = "TRANSACTION_NOT_COMMITTED" ;
24+ static PAYMENT_TX_RETRIES : & [ Duration ] = & [
25+ Duration :: from_secs ( 1 ) ,
26+ Duration :: from_secs ( 2 ) ,
27+ Duration :: from_secs ( 3 ) ,
28+ Duration :: from_secs ( 5 ) ,
29+ Duration :: from_secs ( 10 ) ,
30+ Duration :: from_secs ( 10 ) ,
31+ Duration :: from_secs ( 10 ) ,
32+ ] ;
2033
2134/// An interface to interact with nilauth.
2235#[ async_trait]
@@ -59,8 +72,11 @@ pub enum RequestTokenError {
5972 #[ error( "invalid public key" ) ]
6073 InvalidPublicKey ,
6174
62- #[ error( "request: {0}" ) ]
63- Request ( #[ from] reqwest:: Error ) ,
75+ #[ error( "http: {0}" ) ]
76+ Http ( #[ from] reqwest:: Error ) ,
77+
78+ #[ error( "request: {0:?}" ) ]
79+ Request ( RequestError ) ,
6480}
6581
6682/// An error when paying a subscription.
@@ -78,18 +94,27 @@ pub enum PaySubscriptionError {
7894 #[ error( "invalid public key" ) ]
7995 InvalidPublicKey ,
8096
81- #[ error( "request : {0}" ) ]
82- Request ( #[ from] reqwest:: Error ) ,
97+ #[ error( "http : {0}" ) ]
98+ Http ( #[ from] reqwest:: Error ) ,
8399
84100 #[ error( "making payment: {0}" ) ]
85101 Payment ( String ) ,
102+
103+ #[ error( "server could not validate payment: {tx_hash}" ) ]
104+ PaymentValidation { tx_hash : TxHash , payload : String } ,
105+
106+ #[ error( "request: {0:?}" ) ]
107+ Request ( RequestError ) ,
86108}
87109
88110/// An error when fetching the subscription cost.
89111#[ derive( Debug , thiserror:: Error ) ]
90112pub enum SubscriptionCostError {
91- #[ error( "request: {0}" ) ]
92- Request ( #[ from] reqwest:: Error ) ,
113+ #[ error( "http: {0}" ) ]
114+ Http ( #[ from] reqwest:: Error ) ,
115+
116+ #[ error( "request: {0:?}" ) ]
117+ Request ( RequestError ) ,
93118}
94119
95120/// An error when revoking a token.
@@ -113,24 +138,57 @@ pub enum RevokeTokenError {
113138 #[ error( "building invocation: {0}" ) ]
114139 BuildInvocation ( #[ from] NucTokenBuildError ) ,
115140
116- #[ error( "request: {0}" ) ]
117- Request ( #[ from] reqwest:: Error ) ,
141+ #[ error( "http: {0}" ) ]
142+ Http ( #[ from] reqwest:: Error ) ,
143+
144+ #[ error( "request: {0:?}" ) ]
145+ Request ( RequestError ) ,
118146}
119147
120148/// An error when requesting the information about a nilauth instance.
121149#[ derive( Debug , thiserror:: Error ) ]
122150pub enum AboutError {
123- #[ error( "request: {0}" ) ]
124- Request ( #[ from] reqwest:: Error ) ,
151+ #[ error( "http: {0}" ) ]
152+ Http ( #[ from] reqwest:: Error ) ,
153+
154+ #[ error( "request: {0:?}" ) ]
155+ Request ( RequestError ) ,
125156}
126157
127158/// An error when looking up revoked tokens.
128159#[ derive( Debug , thiserror:: Error ) ]
129160pub enum LookupRevokedTokensError {
130- #[ error( "request: {0}" ) ]
131- Request ( #[ from] reqwest:: Error ) ,
161+ #[ error( "http: {0}" ) ]
162+ Http ( #[ from] reqwest:: Error ) ,
163+
164+ #[ error( "request: {0:?}" ) ]
165+ Request ( RequestError ) ,
166+ }
167+
168+ // implement `From<RequestError>` for a list of types.
169+ macro_rules! impl_from_request_error {
170+ ( $t: ty) => {
171+ impl From <RequestError > for $t {
172+ fn from( e: RequestError ) -> Self {
173+ Self :: Request ( e)
174+ }
175+ }
176+ } ;
177+ ( $t: ty, $( $rest: ty) ,+) => {
178+ impl_from_request_error!( $t) ;
179+ impl_from_request_error!( $( $rest) ,+) ;
180+ } ;
132181}
133182
183+ impl_from_request_error ! (
184+ RequestTokenError ,
185+ PaySubscriptionError ,
186+ SubscriptionCostError ,
187+ RevokeTokenError ,
188+ AboutError ,
189+ LookupRevokedTokensError
190+ ) ;
191+
134192/// The default nilauth client that hits the actual service.
135193pub struct DefaultNilauthClient {
136194 client : reqwest:: Client ,
@@ -147,14 +205,45 @@ impl DefaultNilauthClient {
147205 let base_url = & self . base_url ;
148206 format ! ( "{base_url}{path}" )
149207 }
208+
209+ async fn parse_reponse < T , E > ( response : Response ) -> Result < T , E >
210+ where
211+ T : DeserializeOwned ,
212+ E : From < reqwest:: Error > + From < RequestError > ,
213+ {
214+ if response. status ( ) . is_success ( ) {
215+ Ok ( response. json ( ) . await ?)
216+ } else {
217+ let error: RequestError = response. json ( ) . await ?;
218+ Err ( error. into ( ) )
219+ }
220+ }
221+
222+ async fn post < R , O , E > ( & self , url : & str , request : & R ) -> Result < O , E >
223+ where
224+ R : Serialize ,
225+ O : DeserializeOwned ,
226+ E : From < reqwest:: Error > + From < RequestError > ,
227+ {
228+ let response = self . client . post ( url) . json ( & request) . send ( ) . await ?;
229+ Self :: parse_reponse ( response) . await
230+ }
231+
232+ async fn get < O , E > ( & self , url : & str ) -> Result < O , E >
233+ where
234+ O : DeserializeOwned ,
235+ E : From < reqwest:: Error > + From < RequestError > ,
236+ {
237+ let response = self . client . get ( url) . send ( ) . await ?;
238+ Self :: parse_reponse ( response) . await
239+ }
150240}
151241
152242#[ async_trait]
153243impl NilauthClient for DefaultNilauthClient {
154244 async fn about ( & self ) -> Result < About , AboutError > {
155245 let url = self . make_url ( "/about" ) ;
156- let about = self . client . get ( url) . send ( ) . await ?. json ( ) . await ?;
157- Ok ( about)
246+ self . get ( & url) . await
158247 }
159248
160249 async fn request_token ( & self , key : & SecretKey ) -> Result < String , RequestTokenError > {
@@ -172,9 +261,8 @@ impl NilauthClient for DefaultNilauthClient {
172261 let request =
173262 CreateNucRequest { public_key, signature : signature. to_bytes ( ) . into ( ) , payload : payload. into_bytes ( ) } ;
174263 let url = self . make_url ( "/api/v1/nucs/create" ) ;
175- let response: CreateNucResponse =
176- self . client . post ( url) . json ( & request) . send ( ) . await ?. error_for_status ( ) ?. json ( ) . await ?;
177- Ok ( response. token )
264+ let response: Result < CreateNucResponse , RequestTokenError > = self . post ( & url, & request) . await ;
265+ Ok ( response?. token )
178266 }
179267
180268 async fn pay_subscription (
@@ -194,15 +282,27 @@ impl NilauthClient for DefaultNilauthClient {
194282
195283 let public_key = key. to_sec1_bytes ( ) . as_ref ( ) . try_into ( ) . map_err ( |_| PaySubscriptionError :: InvalidPublicKey ) ?;
196284 let url = self . make_url ( "/api/v1/payments/validate" ) ;
197- let request = ValidatePaymentRequest { tx_hash : tx_hash. clone ( ) , payload : payload. into_bytes ( ) , public_key } ;
198- self . client . post ( url) . json ( & request) . send ( ) . await ?. error_for_status ( ) ?;
199- Ok ( TxHash ( tx_hash) )
285+ let request =
286+ ValidatePaymentRequest { tx_hash : tx_hash. clone ( ) , payload : payload. as_bytes ( ) . to_vec ( ) , public_key } ;
287+ let tx_hash = TxHash ( tx_hash) ;
288+ for delay in PAYMENT_TX_RETRIES {
289+ let response: Result < ( ) , PaySubscriptionError > = self . post ( & url, & request) . await ;
290+ match response {
291+ Ok ( _) => return Ok ( tx_hash) ,
292+ Err ( PaySubscriptionError :: Request ( e) ) if e. error_code == TX_RETRY_ERROR_CODE => {
293+ warn ! ( "Server could not validate payment, retyring in {delay:?}" ) ;
294+ sleep ( * delay) . await ;
295+ }
296+ Err ( e) => return Err ( e) ,
297+ } ;
298+ }
299+ Err ( PaySubscriptionError :: PaymentValidation { tx_hash, payload } )
200300 }
201301
202302 async fn subscription_cost ( & self ) -> Result < TokenAmount , SubscriptionCostError > {
203303 let url = self . make_url ( "/api/v1/payments/cost" ) ;
204- let response: GetCostResponse = self . client . get ( url) . send ( ) . await ? . error_for_status ( ) ? . json ( ) . await ? ;
205- Ok ( TokenAmount :: Unil ( response. cost_unils ) )
304+ let response: Result < GetCostResponse , SubscriptionCostError > = self . get ( & url) . await ;
305+ Ok ( TokenAmount :: Unil ( response? . cost_unils ) )
206306 }
207307
208308 async fn revoke_token ( & self , token : & NucTokenEnvelope , key : & SecretKey ) -> Result < ( ) , RevokeTokenError > {
@@ -219,8 +319,8 @@ impl NilauthClient for DefaultNilauthClient {
219319 . build ( & key. into ( ) ) ?;
220320 let header_value = format ! ( "Bearer {invocation}" ) ;
221321 let url = self . make_url ( "/api/v1/revocations/revoke" ) ;
222- self . client . post ( url) . header ( "Authorization" , header_value) . send ( ) . await ? . error_for_status ( ) ?;
223- Ok ( ( ) )
322+ let response = self . client . post ( url) . header ( "Authorization" , header_value) . send ( ) . await ?;
323+ Self :: parse_reponse ( response ) . await
224324 }
225325
226326 async fn lookup_revoked_tokens (
@@ -230,16 +330,21 @@ impl NilauthClient for DefaultNilauthClient {
230330 let hashes = iter:: once ( envelope. token ( ) ) . chain ( envelope. proofs ( ) ) . map ( |t| t. compute_hash ( ) ) . collect ( ) ;
231331 let request = LookupRevokedTokensRequest { hashes } ;
232332 let url = self . make_url ( "/api/v1/revocations/lookup" ) ;
233- let response: LookupRevokedTokensResponse =
234- self . client . post ( url) . json ( & request) . send ( ) . await ?. error_for_status ( ) ?. json ( ) . await ?;
235- Ok ( response. revoked )
333+ let response: Result < LookupRevokedTokensResponse , LookupRevokedTokensError > = self . post ( & url, & request) . await ;
334+ Ok ( response?. revoked )
236335 }
237336}
238337
239338/// A transaction hash.
240339#[ derive( Clone , Debug , PartialEq ) ]
241340pub struct TxHash ( pub String ) ;
242341
342+ impl Display for TxHash {
343+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
344+ write ! ( f, "{}" , self . 0 )
345+ }
346+ }
347+
243348/// Information about a nilauth server.
244349#[ derive( Clone , Deserialize ) ]
245350pub struct About {
@@ -327,3 +432,13 @@ pub struct RevokedToken {
327432 /// The timestamp at which the token was revoked.
328433 pub revoked_at : DateTime < Utc > ,
329434}
435+
436+ /// An error when performing a request.
437+ #[ derive( Clone , Debug , Deserialize ) ]
438+ pub struct RequestError {
439+ /// The error message.
440+ pub message : String ,
441+
442+ /// The error code.
443+ pub error_code : String ,
444+ }
0 commit comments