From f04c5cbafecb5a22c9630669cbf97e3abc1d0c66 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 2 Jun 2025 12:50:58 +0200 Subject: [PATCH 1/3] Include proof in status and RPC to verify proof --- dune | 2 +- server/lib/directory.ml | 55 +++++++++++--- server/lib/rollup_node_client.ml | 114 +++++++++++++++++++++++++++--- server/lib/rollup_node_client.mli | 58 ++++++++++++--- server/lib/server.ml | 4 +- 5 files changed, 202 insertions(+), 31 deletions(-) diff --git a/dune b/dune index d182f9c..a6c2efd 100644 --- a/dune +++ b/dune @@ -1 +1 @@ -(dirs :standard \ weeklynet* pandora_images*) +(dirs :standard \ weeklynet* ghostnet* pandora_images*) diff --git a/server/lib/directory.ml b/server/lib/directory.ml index 5385469..20ffc3b 100644 --- a/server/lib/directory.ml +++ b/server/lib/directory.ml @@ -18,6 +18,7 @@ let handle_errors = function | Error (`Bad_gateway_to_rollup_node _ as content) -> EzAPIServer.return_error 502 ~content | Error (`Invalid_input _ as content) -> EzAPIServer.return_error 400 ~content + | Error (`Invalid_proof as content) -> EzAPIServer.return_error 422 ~content | Ok a -> EzAPIServer.return_ok a let register service handler = @@ -56,13 +57,16 @@ let err_502 = let open Json_encoding in Err.make ~code:502 ~name:"bad_gateway" ~encoding: - (obj2 + (obj4 (req "error" (constant "bad_gateway_to_rollup_node")) - (req "message" string)) + (req "path" string) + (opt "message" any_ezjson_value) + (req "kind" string)) ~select:(function - | Some (`Bad_gateway_to_rollup_node e) -> Some ((), e) + | Some (`Bad_gateway_to_rollup_node (p, msg, e)) -> Some ((), p, msg, e) | _ -> None) - ~deselect:(fun ((), e) -> Some (`Bad_gateway_to_rollup_node e)) + ~deselect:(fun ((), p, msg, e) -> + Some (`Bad_gateway_to_rollup_node (p, msg, e))) let err_400 = let open Json_encoding in @@ -74,6 +78,15 @@ let err_400 = | _ -> None) ~deselect:(fun ((), e) -> Some (`Invalid_input e)) +let err_422 = + let open Json_encoding in + Err.make ~code:422 ~name:"invalid_proof" + ~encoding:(obj1 (req "error" (constant "invalid_proof"))) + ~select:(function + | Some `Invalid_proof -> Some () + | _ -> None) + ~deselect:(fun () -> Some `Invalid_proof) + let no_error_service = service ~errors:([] : no_error option Err.case list) let service ?(errors = [err_502]) ?section ?name ?descr ?meth ~output ?params @@ -109,13 +122,16 @@ module Encoding = struct let status = let open Rollup_node_client in conv - (fun (Notarized { timestamp }) -> ((), timestamp)) - (fun ((), timestamp) -> Notarized { timestamp }) - @@ obj2 (req "status" (constant "notarized")) (req "timestamp" timestamp) + (fun (Notarized { timestamp; proof }) -> ((), timestamp, proof)) + (fun ((), timestamp, proof) -> Notarized { timestamp; proof }) + @@ obj3 + (req "status" (constant "notarized")) + (req "timestamp" timestamp) + (req "proof" any_ezjson_value) let status_example = let timestamp = Ptime.of_float_s 1111111111. |> Option.get in - Rollup_node_client.Notarized { timestamp } + Rollup_node_client.Notarized { timestamp; proof = `O [] } let hash_example = "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b" @@ -273,6 +289,29 @@ module Notarization_status = struct let () = register service handler end +module Verify_notarization = struct + let service : + (string * Ezjsonm.value, Ptime.t, _, Security.scheme) post_service0 = + post_service ~section ~name:"Verify notarization" + ~errors:[err_502; err_400; err_422] + ~descr:"Verify a notarization proof for a given hash" + ~input: + Json_encoding.( + obj2 (req "hash" Encoding.hash) (req "proof" any_ezjson_value)) + ~output:Json_encoding.(obj1 (req "notarized" Encoding.timestamp)) + ~params:[Param.block] + Path.(root // "notarize" // "verify") + + let handler state params _ (hash, proof) = + let*? hash = Encoding.parse_hex hash in + let block = + Option.bind (Req.find_param Param.block params) Arg.block_id_of_string + in + Rollup_node_client.verify_proof state.State.config ?block hash proof + + let () = register service handler +end + module Openapi = struct let service : (Ezjsonm.value, _, Security.scheme) service0 = no_error_service ~section ~name:"OpenAPI spec" diff --git a/server/lib/rollup_node_client.ml b/server/lib/rollup_node_client.ml index 6bbabc7..0b28de8 100644 --- a/server/lib/rollup_node_client.ml +++ b/server/lib/rollup_node_client.ml @@ -17,7 +17,11 @@ type block_id = type msg_id = Msg_id of string -type status = Notarized of { timestamp : Ptime.t } +type status = + | Notarized of { + timestamp : Ptime.t; + proof : Ezjsonm.value; + } type hash_ts = { hash : string; @@ -137,6 +141,37 @@ module Services = struct Path.( root // "global" // "block" /: Arg.block_id // "durable" // "wasm_2_0_0" // "value") + + let _produce_proof : + (Req.t * block_id, unit, Ezjsonm.value, unit, Security.scheme) service = + service ~section ~name:"produce_proof_value" ~params:[Param.key] + ~output:Json_encoding.any_ezjson_value ~register:false + Path.( + root // "global" // "block" /: Arg.block_id // "helpers" // "proofs" + // "state") + + let produce_proof : + (Req.t * block_id, unit, Ezjsonm.value, unit, Security.scheme) service = + service ~section ~name:"produce_proof_value" ~params:[Param.key] + ~output:Json_encoding.any_ezjson_value ~register:false + Path.( + root // "global" // "block" /: Arg.block_id // "durable" // "wasm_2_0_0" + // "proof") + + let verify_proof : + ( Req.t * block_id, + Ezjsonm.value, + bytes option, + unit, + Security.scheme ) + service = + post_service ~section ~name:"produce_proof_value" ~params:[Param.key] + ~input:Json_encoding.any_ezjson_value + ~output:Json_encoding.(option Encoding.hex_bytes) + ~register:false + Path.( + root // "global" // "block" /: Arg.block_id // "durable" // "wasm_2_0_0" + // "verify_proof") end let map_req_error service res = @@ -151,12 +186,16 @@ let map_req_error service res = match EzAPI.Error_codes.error code with | None -> string_of_int code | Some s -> s in - Format.ksprintf - (fun s -> Error (`Bad_gateway_to_rollup_node s)) - "Rollup node request to %s failed: %s [%s]" - (Service.path service.EzAPI.s |> Path.to_string) - (Option.value msg ~default:"") - err + let msg = + match msg with + | None -> None + | Some msg -> ( + match Ezjsonm.value_from_string_result msg with + | Error _ -> Some (`String msg) + | Ok j -> Some j) in + Error + (`Bad_gateway_to_rollup_node + (Service.path service.EzAPI.s |> Path.to_string, msg, err)) let call_get_service0 (config : Configuration.t) ?params service = let+ res = @@ -179,6 +218,13 @@ let call_post_service0 (config : Configuration.t) service ?params input = service ?params ~input in map_req_error service res +let call_post_service1 (config : Configuration.t) service ?params arg input = + let+ res = + EzReq_lwt.post1 + (BASE (Uri.to_string config.rollup_node_addr)) + service ?params arg ~input in + map_req_error service res + let get_rollup_address config = let+? rollup_addr_b58 = call_get_service0 config Services.rollup_address in match Smart_rollup.of_base58 (Base58 rollup_addr_b58) with @@ -195,6 +241,15 @@ let get_durable_storage_value config ?(block = `Head) key = call_get_service1 config Services.durable_storage_value block ~params:[(Param.key, S key)] +let get_proof config ?(block = `Head) key = + call_get_service1 config Services.produce_proof block + ~params:[(Param.key, S key)] + +let verify_proof config ?(block = `Head) key proof = + call_post_service1 config Services.verify_proof block + ~params:[(Param.key, S key)] + proof + let get_balance state ?block pkh = let pkh_raw_base_58 = Tz1.to_raw_base58 pkh in let key = @@ -250,13 +305,50 @@ let notarize_hash state hash = let notarization_status config ?block hash = let (Base58 hash_b58) = Document_hash.(to_base58 (V hash)) in let key = String.concat "/" ["/pandora/state/hashes"; hash_b58; "timestamp"] in - let+? timestamp_bytes = get_durable_storage_value config ?block key in + let*? timestamp_bytes = get_durable_storage_value config ?block key in match timestamp_bytes with - | None -> None - | Some b -> + | None -> Lwt.return_ok None + | Some b -> ( + let ts_int64 = EndianBytes.LittleEndian.get_int64 b 0 in + let timestamp = Ptime.of_float_s (Int64.to_float ts_int64) in + match timestamp with + | None -> Lwt.return_ok None + | Some timestamp -> + (* let key = String.concat "/" ["/pandora/state/hashes"; hash_b58] in *) + let* proof = get_proof config ?block key in + let proof = + match proof with + | Error _ -> `Null + | Ok p -> p in + Lwt.return_ok (Some (Notarized { timestamp; proof }))) + +let verify_proof config ?block hash proof = + let (Base58 hash_b58) = Document_hash.(to_base58 (V hash)) in + let key = String.concat "/" ["/pandora/state/hashes"; hash_b58; "timestamp"] in + let* content = verify_proof config ?block key proof in + match content with + | Error (`Bad_gateway_to_rollup_node (_, Some err, _) as e) -> + let bad_proof = + try + let err = + Ezjsonm.get_list + (fun j -> Ezjsonm.find j ["msg"] |> Ezjsonm.get_string) + err + |> List.hd in + String.starts_with err ~prefix:"Could not verify state proof" + with _ -> false in + if bad_proof then + Lwt.return_error `Invalid_proof + else + Lwt.return_error e + | Error e -> Lwt.return_error e + | Ok None -> Lwt.return_error `Invalid_proof + | Ok (Some b) -> ( let ts_int64 = EndianBytes.LittleEndian.get_int64 b 0 in let timestamp = Ptime.of_float_s (Int64.to_float ts_int64) in - Option.map (fun timestamp -> Notarized { timestamp }) timestamp + match timestamp with + | None -> failwith "Invalid timestamp in proof" + | Some timestamp -> Lwt.return_ok timestamp) (* This should probably be moved into a seperate file at some point. *) let hashrecords_cache : hashes_ts ref = ref [] diff --git a/server/lib/rollup_node_client.mli b/server/lib/rollup_node_client.mli index 2d64248..50d63c7 100644 --- a/server/lib/rollup_node_client.mli +++ b/server/lib/rollup_node_client.mli @@ -16,7 +16,11 @@ type block_id = type msg_id = Msg_id of string (** Status of notarization *) -type status = Notarized of { timestamp : Ptime.t } +type status = + | Notarized of { + timestamp : Ptime.t; + proof : Ezjsonm.value; + } (** Hash with its respective timestamp *) type hash_ts = { @@ -58,34 +62,52 @@ end (** Retrieve rollup address which the rollup node handles *) val get_rollup_address : Configuration.t -> - (Smart_rollup.t, [> `Bad_gateway_to_rollup_node of string]) Lwt_result.t + ( Smart_rollup.t, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + Lwt_result.t (** Inject messages to the batcher and return a list of identifiers to be used by {!get_message_status}. *) val inject_messages : Configuration.t -> string list -> - (msg_id list, [> `Bad_gateway_to_rollup_node of string]) result Lwt.t + ( msg_id list, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + result + Lwt.t (** Retrieve the status of the message in the rollup node. *) val get_message_status : Configuration.t -> msg_id -> - (Json_repr.ezjsonm, [> `Bad_gateway_to_rollup_node of string]) result Lwt.t + ( Json_repr.ezjsonm, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + result + Lwt.t (** Retrieve a value associated to a given key in the durable storage. *) val get_durable_storage_value : Configuration.t -> ?block:block_id -> string -> - (bytes option, [> `Bad_gateway_to_rollup_node of string]) result Lwt.t + ( bytes option, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + result + Lwt.t (** Retrieve balance for account on the pandora rollup. *) val get_balance : Configuration.t -> ?block:block_id -> Tz1.t -> - (int64 option, [> `Bad_gateway_to_rollup_node of string]) Lwt_result.t + ( int64 option, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + Lwt_result.t (** Create external message with signature for hash. *) val make_external_message : State.t -> string -> string @@ -94,14 +116,31 @@ val make_external_message : State.t -> string -> string val notarize_hash : State.t -> string -> - (unit, [> `Bad_gateway_to_rollup_node of string]) Lwt_result.t + ( unit, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + Lwt_result.t (** Returns the notarization status for a given hash. *) val notarization_status : Configuration.t -> ?block:block_id -> string -> - (status option, [> `Bad_gateway_to_rollup_node of string]) Lwt_result.t + ( status option, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + Lwt_result.t + +val verify_proof : + Configuration.t -> + ?block:block_id -> + string -> + Ezjsonm.value -> + ( Ptime.t, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string + | `Invalid_proof ] ) + result + Lwt.t (** Returns all the notarized hashes with their respective timestamps. *) val all_hashes : @@ -109,5 +148,6 @@ val all_hashes : ?block:block_id -> unit -> ( hashes_ts, - [> `Bad_gateway_to_rollup_node of string | `Internal_error of string] ) + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string + | `Internal_error of string ] ) Lwt_result.t diff --git a/server/lib/server.ml b/server/lib/server.ml index 60c107e..0853d6c 100644 --- a/server/lib/server.ml +++ b/server/lib/server.ml @@ -11,8 +11,8 @@ let init_state config = let rollup = match rollup with | Ok r -> r - | Error (`Bad_gateway_to_rollup_node e) -> - Format.ksprintf failwith "Could not retrieve rollup address: %s" e in + | Error (`Bad_gateway_to_rollup_node _) -> + failwith "Could not retrieve rollup address" in { State.config; rollup } let start config = -- GitLab From c550e8f7a916a4b5eedd30bc16a6cb6aa305ecd0 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 2 Jun 2025 13:13:28 +0200 Subject: [PATCH 2/3] Proof in separate RPC --- server/lib/directory.ml | 29 ++++++++++++++++++++++------- server/lib/rollup_node_client.ml | 20 +++++++------------- server/lib/rollup_node_client.mli | 16 +++++++++++----- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/server/lib/directory.ml b/server/lib/directory.ml index 20ffc3b..3cfa850 100644 --- a/server/lib/directory.ml +++ b/server/lib/directory.ml @@ -122,16 +122,13 @@ module Encoding = struct let status = let open Rollup_node_client in conv - (fun (Notarized { timestamp; proof }) -> ((), timestamp, proof)) - (fun ((), timestamp, proof) -> Notarized { timestamp; proof }) - @@ obj3 - (req "status" (constant "notarized")) - (req "timestamp" timestamp) - (req "proof" any_ezjson_value) + (fun (Notarized { timestamp }) -> ((), timestamp)) + (fun ((), timestamp) -> Notarized { timestamp }) + @@ obj2 (req "status" (constant "notarized")) (req "timestamp" timestamp) let status_example = let timestamp = Ptime.of_float_s 1111111111. |> Option.get in - Rollup_node_client.Notarized { timestamp; proof = `O [] } + Rollup_node_client.Notarized { timestamp } let hash_example = "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b" @@ -289,6 +286,24 @@ module Notarization_status = struct let () = register service handler end +module Notarization_proof = struct + let service : (string, Ezjsonm.value, _, Security.scheme) post_service0 = + post_service ~section ~name:"Notarization proof" ~errors:[err_502; err_400] + ~descr:"Compute a notarization proof for a given hash" + ~input:Encoding.hash ~input_example:Encoding.hash_example + ~output:Json_encoding.any_ezjson_value ~params:[Param.block] + Path.(root // "notarize" // "proof") + + let handler state params _ hash = + let*? hash = Encoding.parse_hex hash in + let block = + Option.bind (Req.find_param Param.block params) Arg.block_id_of_string + in + Rollup_node_client.get_proof state.State.config ?block hash + + let () = register service handler +end + module Verify_notarization = struct let service : (string * Ezjsonm.value, Ptime.t, _, Security.scheme) post_service0 = diff --git a/server/lib/rollup_node_client.ml b/server/lib/rollup_node_client.ml index 0b28de8..c50f372 100644 --- a/server/lib/rollup_node_client.ml +++ b/server/lib/rollup_node_client.ml @@ -17,11 +17,7 @@ type block_id = type msg_id = Msg_id of string -type status = - | Notarized of { - timestamp : Ptime.t; - proof : Ezjsonm.value; - } +type status = Notarized of { timestamp : Ptime.t } type hash_ts = { hash : string; @@ -313,14 +309,12 @@ let notarization_status config ?block hash = let timestamp = Ptime.of_float_s (Int64.to_float ts_int64) in match timestamp with | None -> Lwt.return_ok None - | Some timestamp -> - (* let key = String.concat "/" ["/pandora/state/hashes"; hash_b58] in *) - let* proof = get_proof config ?block key in - let proof = - match proof with - | Error _ -> `Null - | Ok p -> p in - Lwt.return_ok (Some (Notarized { timestamp; proof }))) + | Some timestamp -> Lwt.return_ok (Some (Notarized { timestamp }))) + +let get_proof config ?block hash = + let (Base58 hash_b58) = Document_hash.(to_base58 (V hash)) in + let key = String.concat "/" ["/pandora/state/hashes"; hash_b58; "timestamp"] in + get_proof config ?block key let verify_proof config ?block hash proof = let (Base58 hash_b58) = Document_hash.(to_base58 (V hash)) in diff --git a/server/lib/rollup_node_client.mli b/server/lib/rollup_node_client.mli index 50d63c7..3028d25 100644 --- a/server/lib/rollup_node_client.mli +++ b/server/lib/rollup_node_client.mli @@ -16,11 +16,7 @@ type block_id = type msg_id = Msg_id of string (** Status of notarization *) -type status = - | Notarized of { - timestamp : Ptime.t; - proof : Ezjsonm.value; - } +type status = Notarized of { timestamp : Ptime.t } (** Hash with its respective timestamp *) type hash_ts = { @@ -131,6 +127,16 @@ val notarization_status : ) Lwt_result.t +val get_proof : + Configuration.t -> + ?block:block_id -> + string -> + ( Ezjsonm.value, + [> `Bad_gateway_to_rollup_node of string * Ezjsonm.value option * string] + ) + result + Lwt.t + val verify_proof : Configuration.t -> ?block:block_id -> -- GitLab From 59c61afced7d0fc0a5d2095d6e6e8d99edc56f18 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 2 Jun 2025 13:48:21 +0200 Subject: [PATCH 3/3] Return hash with proof --- server/lib/directory.ml | 15 ++++++++++----- server/lib/rollup_node_client.ml | 16 ++-------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/server/lib/directory.ml b/server/lib/directory.ml index 3cfa850..8c88c6e 100644 --- a/server/lib/directory.ml +++ b/server/lib/directory.ml @@ -287,19 +287,24 @@ module Notarization_status = struct end module Notarization_proof = struct - let service : (string, Ezjsonm.value, _, Security.scheme) post_service0 = + let service : + (string, string * Ezjsonm.value, _, Security.scheme) post_service0 = post_service ~section ~name:"Notarization proof" ~errors:[err_502; err_400] ~descr:"Compute a notarization proof for a given hash" ~input:Encoding.hash ~input_example:Encoding.hash_example - ~output:Json_encoding.any_ezjson_value ~params:[Param.block] + ~output: + Json_encoding.( + obj2 (req "hash" Encoding.hash) (req "proof" any_ezjson_value)) + ~params:[Param.block] Path.(root // "notarize" // "proof") - let handler state params _ hash = - let*? hash = Encoding.parse_hex hash in + let handler state params _ hex_hash = + let*? hash = Encoding.parse_hex hex_hash in let block = Option.bind (Req.find_param Param.block params) Arg.block_id_of_string in - Rollup_node_client.get_proof state.State.config ?block hash + let+? proof = Rollup_node_client.get_proof state.State.config ?block hash in + (hex_hash, proof) let () = register service handler end diff --git a/server/lib/rollup_node_client.ml b/server/lib/rollup_node_client.ml index c50f372..eac49a6 100644 --- a/server/lib/rollup_node_client.ml +++ b/server/lib/rollup_node_client.ml @@ -321,20 +321,8 @@ let verify_proof config ?block hash proof = let key = String.concat "/" ["/pandora/state/hashes"; hash_b58; "timestamp"] in let* content = verify_proof config ?block key proof in match content with - | Error (`Bad_gateway_to_rollup_node (_, Some err, _) as e) -> - let bad_proof = - try - let err = - Ezjsonm.get_list - (fun j -> Ezjsonm.find j ["msg"] |> Ezjsonm.get_string) - err - |> List.hd in - String.starts_with err ~prefix:"Could not verify state proof" - with _ -> false in - if bad_proof then - Lwt.return_error `Invalid_proof - else - Lwt.return_error e + | Error (`Bad_gateway_to_rollup_node (_, _, "Internal Server Error")) -> + Lwt.return_error `Invalid_proof | Error e -> Lwt.return_error e | Ok None -> Lwt.return_error `Invalid_proof | Ok (Some b) -> ( -- GitLab