@@ -25,8 +25,8 @@ use crate::{
2525 db:: Db ,
2626 helpers:: get_time_left_day_formatted,
2727 metrics:: GatewayMetrics ,
28- types:: { GetReceiptsResponse , SubmitProofRequestRisc0 , SubmitProofRequestSP1 } ,
29- verifiers:: { verify_sp1_proof, VerificationError } ,
28+ types:: { GetReceiptsResponse , SubmitProofRequestRisc0 , SubmitProofRequestSP1 , SubmitProofRequestZisk } ,
29+ verifiers:: { verify_sp1_proof, verify_zisk_proof , VerificationError } ,
3030} ;
3131
3232#[ derive( Clone , Debug ) ]
@@ -78,6 +78,7 @@ impl GatewayServer {
7878 . route ( "/receipts" , web:: get ( ) . to ( Self :: get_receipts) )
7979 . route ( "/proof/sp1" , web:: post ( ) . to ( Self :: post_proof_sp1) )
8080 . route ( "/proof/risc0" , web:: post ( ) . to ( Self :: post_proof_risc0) )
81+ . route ( "/proof/zisk" , web:: post ( ) . to ( Self :: post_proof_zisk) )
8182 . route ( "/quotas/{address}" , web:: get ( ) . to ( Self :: get_quotas) )
8283 } )
8384 . bind ( ( self . config . ip . as_str ( ) , port) )
@@ -285,6 +286,142 @@ impl GatewayServer {
285286 HttpResponse :: Ok ( ) . json ( AppResponse :: new_sucessfull ( serde_json:: json!( { } ) ) )
286287 }
287288
289+ // Posts a Zisk proof to the gateway, recovering the address from the signature
290+ async fn post_proof_zisk (
291+ req : HttpRequest ,
292+ MultipartForm ( data) : MultipartForm < SubmitProofRequestZisk > ,
293+ ) -> impl Responder {
294+ let Some ( state) = req. app_data :: < Data < GatewayServer > > ( ) else {
295+ return HttpResponse :: InternalServerError ( )
296+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
297+ } ;
298+
299+ let state = state. get_ref ( ) ;
300+ let Ok ( signature) = Signature :: from_str ( & data. signature_hex . 0 ) else {
301+ return HttpResponse :: InternalServerError ( )
302+ . json ( AppResponse :: new_unsucessfull ( "Invalid signature" , 500 ) ) ;
303+ } ;
304+
305+ let Ok ( proof_content) = tokio:: fs:: read ( data. proof . file . path ( ) ) . await else {
306+ return HttpResponse :: InternalServerError ( )
307+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
308+ } ;
309+
310+ // reconstruct message and recover address
311+ let msg = agg_mode_sdk:: gateway:: types:: SubmitZiskProofMessage :: new (
312+ data. nonce . 0 ,
313+ proof_content. clone ( ) ,
314+ ) ;
315+ let Ok ( recovered_address) =
316+ signature. recover_address_from_prehash ( & msg. eip712_hash ( & state. network ) . into ( ) )
317+ else {
318+ return HttpResponse :: InternalServerError ( )
319+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
320+ } ;
321+ let recovered_address = recovered_address. to_string ( ) . to_lowercase ( ) ;
322+
323+ // Checking if this address has submited more proofs than the ones allowed per day
324+ let Ok ( daily_tasks_by_address) = state
325+ . db
326+ . get_daily_tasks_by_address ( & recovered_address)
327+ . await
328+ else {
329+ return HttpResponse :: InternalServerError ( )
330+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
331+ } ;
332+
333+ if daily_tasks_by_address >= state. config . max_daily_proofs_per_user {
334+ let formatted_time_left = get_time_left_day_formatted ( ) ;
335+
336+ return HttpResponse :: BadRequest ( ) . json ( AppResponse :: new_unsucessfull (
337+ format ! (
338+ "Request denied: Query limit exceeded. Quotas renew in {formatted_time_left}"
339+ )
340+ . as_str ( ) ,
341+ 400 ,
342+ ) ) ;
343+ }
344+
345+ let Ok ( count) = state. db . count_tasks_by_address ( & recovered_address) . await else {
346+ return HttpResponse :: InternalServerError ( )
347+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
348+ } ;
349+
350+ if data. nonce . 0 != ( count as u64 ) {
351+ return HttpResponse :: BadRequest ( ) . json ( AppResponse :: new_unsucessfull (
352+ & format ! ( "Invalid nonce, expected nonce = {count}" ) ,
353+ 400 ,
354+ ) ) ;
355+ }
356+
357+ let now_epoch = match SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) {
358+ Ok ( duration) => duration. as_secs ( ) ,
359+ Err ( _) => {
360+ return HttpResponse :: InternalServerError ( )
361+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
362+ }
363+ } ;
364+
365+ let has_payment = match state
366+ . db
367+ . has_active_payment_event (
368+ & recovered_address,
369+ BigDecimal :: from_str ( & now_epoch. to_string ( ) ) . unwrap ( ) ,
370+ )
371+ . await
372+ {
373+ Ok ( result) => result,
374+ Err ( _) => {
375+ return HttpResponse :: InternalServerError ( )
376+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
377+ }
378+ } ;
379+
380+ if !has_payment {
381+ return HttpResponse :: BadRequest ( ) . json ( AppResponse :: new_unsucessfull (
382+ "You have to pay before submitting a proof" ,
383+ 400 ,
384+ ) ) ;
385+ }
386+
387+ if let Err ( e) = verify_zisk_proof ( & proof_content) {
388+ let message = match e {
389+ VerificationError :: InvalidProof => "Proof verification failed" ,
390+ VerificationError :: UnsupportedProof => "Unsupported proof" ,
391+ } ;
392+
393+ return HttpResponse :: BadRequest ( ) . json ( AppResponse :: new_unsucessfull ( message, 400 ) ) ;
394+ } ;
395+
396+ let query_started_at = Instant :: now ( ) ;
397+
398+ match state
399+ . db
400+ . insert_task (
401+ & recovered_address,
402+ AggregationModeProvingSystem :: ZISK . as_u16 ( ) as i32 ,
403+ & proof_content,
404+ & [ ] , // Zisk proofs don't have a separate vk file
405+ None ,
406+ data. nonce . 0 as i64 ,
407+ )
408+ . await
409+ {
410+ Ok ( task_id) => {
411+ let time_elapsed_db_call = query_started_at. elapsed ( ) ;
412+ state
413+ . metrics
414+ . register_db_response_time_post ( "zisk-post" , time_elapsed_db_call. as_secs_f64 ( ) ) ;
415+
416+ HttpResponse :: Ok ( ) . json ( AppResponse :: new_sucessfull (
417+ serde_json:: json!( { "task_id" : task_id. to_string( ) } ) ,
418+ ) )
419+ }
420+ Err ( _) => HttpResponse :: InternalServerError ( )
421+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ,
422+ }
423+ }
424+
288425 // Returns the last 100 receipt merkle proofs for the address received in the URL.
289426 // In case of also receiving a nonce on the query param, it returns only the merkle proof for that nonce.
290427 async fn get_receipts (
0 commit comments