11//! STAR Randomness web service route implementation
22
3- use axum:: extract:: { Json , State } ;
3+ use std:: sync:: RwLockReadGuard ;
4+
5+ use axum:: extract:: { Json , Path , State } ;
46use axum:: http:: StatusCode ;
57use base64:: prelude:: { Engine as _, BASE64_STANDARD as BASE64 } ;
68use serde:: { Deserialize , Serialize } ;
7- use tracing:: debug;
9+ use tracing:: { debug, instrument } ;
810
9- use crate :: OPRFState ;
11+ use crate :: state :: { OPRFInstance , OPRFState } ;
1012use ppoprf:: ppoprf;
1113
12- /// Request format for the randomness endpoint
14+ /// Request structure for the randomness endpoint
1315#[ derive( Deserialize , Debug ) ]
1416pub struct RandomnessRequest {
1517 /// Array of points to evaluate
@@ -19,7 +21,7 @@ pub struct RandomnessRequest {
1921 epoch : Option < u8 > ,
2022}
2123
22- /// Response format for the randomness endpoint
24+ /// Response structure for the randomness endpoint
2325#[ derive( Serialize , Debug ) ]
2426pub struct RandomnessResponse {
2527 /// Resulting points from the OPRF valuation
@@ -30,26 +32,34 @@ pub struct RandomnessResponse {
3032 epoch : u8 ,
3133}
3234
33- /// Response format for the info endpoint
35+ /// Response structure for the info endpoint
3436/// Rename fields to match the earlier golang implementation.
3537#[ derive( Serialize , Debug ) ]
38+ #[ serde( rename_all = "camelCase" ) ]
3639pub struct InfoResponse {
3740 /// ServerPublicKey used to verify zero-knowledge proof
38- #[ serde( rename = "publicKey" ) ]
3941 public_key : String ,
4042 /// Currently active randomness epoch
41- #[ serde( rename = "currentEpoch" ) ]
4243 current_epoch : u8 ,
4344 /// Timestamp of the next epoch rotation
4445 /// This should be a string in RFC 3339 format,
4546 /// e.g. 2023-03-14T16:33:05Z.
46- #[ serde( rename = "nextEpochTime" ) ]
4747 next_epoch_time : Option < String > ,
4848 /// Maximum number of points accepted in a single request
49- #[ serde( rename = "maxPoints" ) ]
5049 max_points : usize ,
5150}
5251
52+ /// Response structure for the "list instances" endpoint.
53+ #[ derive( Serialize , Debug ) ]
54+ #[ serde( rename_all = "camelCase" ) ]
55+ pub struct ListInstancesResponse {
56+ /// A list of available instances on the server.
57+ instances : Vec < String > ,
58+ /// The default instance on this server.
59+ /// A requests made to /info and /randomness will utilize this instance.
60+ default_instance : String ,
61+ }
62+
5363/// Response returned to report error conditions
5464#[ derive( Serialize , Debug ) ]
5565struct ErrorResponse {
@@ -63,6 +73,8 @@ struct ErrorResponse {
6373/// handling requests.
6474#[ derive( thiserror:: Error , Debug ) ]
6575pub enum Error {
76+ #[ error( "instance '{0}' not found" ) ]
77+ InstanceNotFound ( String ) ,
6678 #[ error( "Couldn't lock state: RwLock poisoned" ) ]
6779 LockFailure ,
6880 #[ error( "Invalid point" ) ]
@@ -93,6 +105,7 @@ impl axum::response::IntoResponse for Error {
93105 /// Construct an http response from our error type
94106 fn into_response ( self ) -> axum:: response:: Response {
95107 let code = match self {
108+ Error :: InstanceNotFound ( _) => StatusCode :: NOT_FOUND ,
96109 // This indicates internal failure.
97110 Error :: LockFailure => StatusCode :: INTERNAL_SERVER_ERROR ,
98111 // Other cases are the client's fault.
@@ -105,13 +118,28 @@ impl axum::response::IntoResponse for Error {
105118 }
106119}
107120
121+ type Result < T > = std:: result:: Result < T , Error > ;
122+
123+ fn get_server_from_state < ' a > (
124+ state : & ' a OPRFState ,
125+ instance_name : & ' a str ,
126+ ) -> Result < RwLockReadGuard < ' a , OPRFInstance > > {
127+ Ok ( state
128+ . instances
129+ . get ( instance_name)
130+ . ok_or_else ( || Error :: InstanceNotFound ( instance_name. to_string ( ) ) ) ?
131+ . read ( ) ?)
132+ }
133+
108134/// Process PPOPRF evaluation requests
109- pub async fn randomness (
110- State ( state) : State < OPRFState > ,
111- Json ( request) : Json < RandomnessRequest > ,
112- ) -> Result < Json < RandomnessResponse > , Error > {
135+ #[ instrument( skip( state, request) ) ]
136+ async fn randomness (
137+ state : OPRFState ,
138+ instance_name : String ,
139+ request : RandomnessRequest ,
140+ ) -> Result < Json < RandomnessResponse > > {
113141 debug ! ( "recv: {request:?}" ) ;
114- let state = state . read ( ) ?;
142+ let state = get_server_from_state ( & state , & instance_name ) ?;
115143 let epoch = request. epoch . unwrap_or ( state. epoch ) ;
116144 if epoch != state. epoch {
117145 return Err ( Error :: BadEpoch ( epoch) ) ;
@@ -138,12 +166,29 @@ pub async fn randomness(
138166 Ok ( Json ( response) )
139167}
140168
141- /// Process PPOPRF epoch and key requests
142- pub async fn info (
169+ /// Process PPOPRF evaluation requests using default instance
170+ pub async fn default_instance_randomness (
143171 State ( state) : State < OPRFState > ,
144- ) -> Result < Json < InfoResponse > , Error > {
172+ Json ( request) : Json < RandomnessRequest > ,
173+ ) -> Result < Json < RandomnessResponse > > {
174+ let instance_name = state. default_instance . clone ( ) ;
175+ randomness ( state, instance_name, request) . await
176+ }
177+
178+ /// Process PPOPRF evaluation requests using specific instance
179+ pub async fn specific_instance_randomness (
180+ State ( state) : State < OPRFState > ,
181+ Path ( instance_name) : Path < String > ,
182+ Json ( request) : Json < RandomnessRequest > ,
183+ ) -> Result < Json < RandomnessResponse > > {
184+ randomness ( state, instance_name, request) . await
185+ }
186+
187+ /// Provide PPOPRF epoch and key metadata
188+ #[ instrument( skip( state) ) ]
189+ async fn info ( state : OPRFState , instance_name : String ) -> Result < Json < InfoResponse > > {
145190 debug ! ( "recv: info request" ) ;
146- let state = state . read ( ) ?;
191+ let state = get_server_from_state ( & state , & instance_name ) ?;
147192 let public_key = state. server . get_public_key ( ) . serialize_to_bincode ( ) ?;
148193 let public_key = BASE64 . encode ( public_key) ;
149194 let response = InfoResponse {
@@ -155,3 +200,25 @@ pub async fn info(
155200 debug ! ( "send: {response:?}" ) ;
156201 Ok ( Json ( response) )
157202}
203+
204+ /// Provide PPOPRF epoch and key metadata using default instance
205+ pub async fn default_instance_info ( State ( state) : State < OPRFState > ) -> Result < Json < InfoResponse > > {
206+ let instance_name = state. default_instance . clone ( ) ;
207+ info ( state, instance_name) . await
208+ }
209+
210+ /// Provide PPOPRF epoch and key metadata using specific instance
211+ pub async fn specific_instance_info (
212+ State ( state) : State < OPRFState > ,
213+ Path ( instance_name) : Path < String > ,
214+ ) -> Result < Json < InfoResponse > > {
215+ info ( state, instance_name) . await
216+ }
217+
218+ // Lists all available instances, as well as the default instance
219+ pub async fn list_instances ( State ( state) : State < OPRFState > ) -> Result < Json < ListInstancesResponse > > {
220+ Ok ( Json ( ListInstancesResponse {
221+ instances : state. instances . keys ( ) . cloned ( ) . collect ( ) ,
222+ default_instance : state. default_instance . clone ( ) ,
223+ } ) )
224+ }
0 commit comments