Repository: MinaProtocol/mina
Pull request #16931: Simplify code of verifier
Author: login: georgeee
Created: 2025-04-12T14:59:15Z | Merged: 2025-04-14T19:27:14Z

Simplify the verifier's logic (don't use integer tags), relax types in
Common.check.

Previous logic was unnecessarily complex, with returned types later ignored, complex logic of sorting instead of `map2_exn` etc.

Explain how you tested your changes:
* A refactoring, no behavior changes.

Checklist:

- [x] Dependency versions are unchanged
  - Notify Velocity team if dependencies must change in CI
- [x] Modified the current draft of release notes with details on what is completed or incomplete within this project
- [x] Document code purpose, how to use it
  - Mention expected invariants, implicit constraints
- [x] Tests were added for the new behavior
  - Document test purpose, significance of failures
  - Test names should reflect their purpose
- [x] All tests pass (CI will check this if you didn't)
- [x] Serialized types are in stable-versioned modules
- [x] Does this close issues? None



Diff:
diff --git a/src/lib/verifier/common.ml b/src/lib/verifier/common.ml
index bec3913d08d9..373814701abe 100644
--- a/src/lib/verifier/common.ml
+++ b/src/lib/verifier/common.ml
@@ -39,8 +39,8 @@ let check_signed_command c =
     Result.Error (`Invalid_keys (Signed_command.public_keys c))
   else
     match Signed_command.check_only_for_signature c with
-    | Some c ->
-        Result.Ok (User_command.Signed_command c, `Assuming [])
+    | Some _ ->
+        Result.Ok (`Assuming [])
     | None ->
         Result.Error (`Invalid_signature (Signed_command.public_keys c))
 
@@ -67,20 +67,22 @@ let collect_vk_assumption
   | _ ->
       Ok None
 
-let collect_vk_assumptions (zkapp_command : Zkapp_command.Verifiable.t) =
+let collect_vk_assumptions zkapp_command =
   let collect_vk_assumption' collected (element, _) =
     let%map.Result res_opt = collect_vk_assumption element in
     Option.value_map ~f:(Fn.flip List.cons collected) ~default:collected res_opt
   in
-  zkapp_command.account_updates |> Zkapp_statement.zkapp_statements_of_forest'
+  zkapp_command.Zkapp_command.Poly.account_updates
+  |> Zkapp_statement.zkapp_statements_of_forest'
   |> Zkapp_command.Call_forest.With_hashes_and_data
      .to_zkapp_command_with_hashes_list
   |> List.fold_result ~f:collect_vk_assumption' ~init:[]
 
-let check_signatures_of_zkapp_command (zkapp_command : Zkapp_command.t) :
+let check_signatures_of_zkapp_command (zkapp_command : _ Zkapp_command.Poly.t) :
     (unit, invalid) Result.t =
   let account_updates_hash =
-    Zkapp_command.Call_forest.hash zkapp_command.account_updates
+    Zkapp_command.Call_forest.hash
+      zkapp_command.Zkapp_command.Poly.account_updates
   in
   let tx_commitment =
     Zkapp_command.Transaction_commitment.create ~account_updates_hash
@@ -118,7 +120,7 @@ let check_signatures_of_zkapp_command (zkapp_command : Zkapp_command.t) :
            else tx_commitment
          in
          match (p.authorization, p.body.authorization_kind) with
-         | Signature s, Signature ->
+         | Control.Signature s, Signature ->
              check_signature s p.body.public_key commitment
          | None_given, None_given | Proof _, Proof _ ->
              Ok ()
@@ -127,29 +129,19 @@ let check_signatures_of_zkapp_command (zkapp_command : Zkapp_command.t) :
                (`Mismatched_authorization_kind
                  [ Account_id.public_key @@ Account_update.account_id p ] ) )
 
-let check :
-       User_command.Verifiable.t With_status.t
-    -> (User_command.Valid.t * [ `Assuming of _ list ], invalid) Result.t =
-  let zkapp_command_to_valid zkapp_command : User_command.Valid.t =
-    (* Verification keys should be present if it reaches here *)
-    let (`If_this_is_used_it_should_have_a_comment_justifying_it
-          valid_zkapp_command ) =
-      Zkapp_command.Valid.to_valid_unsafe zkapp_command
-    in
-    User_command.Poly.Zkapp_command valid_zkapp_command
-  in
+let check : _ With_status.t -> ([ `Assuming of _ list ], invalid) Result.t =
   function
   | { With_status.data = User_command.Signed_command c; status = _ } ->
       check_signed_command c
   | { With_status.data = Zkapp_command verifiable; status = Failed _ } ->
       let command = Zkapp_command.of_verifiable verifiable in
       let%map.Result () = check_signatures_of_zkapp_command command in
-      (zkapp_command_to_valid command, `Assuming [])
+      `Assuming []
   | { With_status.data = Zkapp_command verifiable; status = Applied } ->
       let command = Zkapp_command.of_verifiable verifiable in
       let%bind.Result () = check_signatures_of_zkapp_command command in
       let%map.Result assuming = collect_vk_assumptions verifiable in
-      (zkapp_command_to_valid command, `Assuming assuming)
+      `Assuming assuming
 
 (** Verifies a command that is being held in mempool.
   * Function only assumes that `User_command.t` is held in the mempool,
diff --git a/src/lib/verifier/dummy.ml b/src/lib/verifier/dummy.ml
index 1ec39d0543e2..f24d83cf8c21 100644
--- a/src/lib/verifier/dummy.ml
+++ b/src/lib/verifier/dummy.ml
@@ -91,56 +91,46 @@ let verify_commands { proof_level; _ }
     | Common.invalid ]
     list
     Deferred.Or_error.t =
+  let valid { With_status.data = cmd; _ } =
+    (* Since we have stripped the transaction from the result, we reconstruct it here.
+       The use of [to_valid_unsafe] is justified because a [`Valid] result for this
+       command means that it has indeed been validated. *)
+    let (`If_this_is_used_it_should_have_a_comment_justifying_it cmd') =
+      User_command.(cmd |> of_verifiable |> to_valid_unsafe)
+    in
+    `Valid cmd'
+  in
   match proof_level with
   | Check | No_check ->
-      List.map cs ~f:(fun c ->
-          match Common.check c with
-          | Ok (c, `Assuming _) ->
-              `Valid c
-          | Error (`Invalid_keys keys) ->
-              `Invalid_keys keys
-          | Error (`Invalid_signature keys) ->
-              `Invalid_signature keys
-          | Error (`Invalid_proof err) ->
-              `Invalid_proof err
-          | Error (`Missing_verification_key keys) ->
-              `Missing_verification_key keys
-          | Error (`Unexpected_verification_key keys) ->
-              `Unexpected_verification_key keys
-          | Error (`Mismatched_authorization_kind keys) ->
-              `Mismatched_authorization_kind keys )
-      |> Deferred.Or_error.return
+      let convert_check_res cmd : _ -> [> invalid | `Valid of _ ] = function
+        | Error (#invalid as invalid) ->
+            invalid
+        | Ok (`Assuming _) ->
+            valid cmd
+      in
+      let f cmd = convert_check_res cmd (Common.check cmd) in
+      List.map cs ~f |> Deferred.Or_error.return
   | Full ->
-      let cs = List.map cs ~f:Common.check in
+      let results = List.map cs ~f:Common.check in
       let to_verify =
-        List.concat_map cs ~f:(function
-          | Ok (_, `Assuming xs) ->
-              xs
-          | Error _ ->
-              [] )
+        List.concat_map
+          ~f:(function Ok (`Assuming xs) -> xs | Error _ -> [])
+          results
       in
       let%map all_verified =
         Pickles.Side_loaded.verify ~typ:Zkapp_statement.typ to_verify
       in
-      Ok
-        (List.map cs ~f:(function
-          | Ok (c, `Assuming []) ->
-              `Valid c
-          | Ok (c, `Assuming xs) ->
-              if Or_error.is_ok all_verified then `Valid c
-              else `Valid_assuming xs
-          | Error (`Invalid_keys keys) ->
-              `Invalid_keys keys
-          | Error (`Invalid_signature keys) ->
-              `Invalid_signature keys
-          | Error (`Invalid_proof err) ->
-              `Invalid_proof err
-          | Error (`Missing_verification_key keys) ->
-              `Missing_verification_key keys
-          | Error (`Unexpected_verification_key keys) ->
-              `Unexpected_verification_key keys
-          | Error (`Mismatched_authorization_kind keys) ->
-              `Mismatched_authorization_kind keys ) )
+      let f cmd : _ -> [ invalid | `Valid of _ | `Valid_assuming of _ ] =
+        function
+        | Error (#invalid as invalid) ->
+            invalid
+        | Ok (`Assuming []) ->
+            valid cmd
+        | Ok (`Assuming xs) ->
+            if Or_error.is_ok all_verified then valid cmd
+            else `Valid_assuming xs
+      in
+      Ok (List.map2_exn cs results ~f)
 
 let verify_transaction_snarks { verify_transaction_snarks; _ } ts =
   verify_transaction_snarks ts
diff --git a/src/lib/verifier/prod.ml b/src/lib/verifier/prod.ml
index 7bc4d04cacd4..98b24249adf0 100644
--- a/src/lib/verifier/prod.ml
+++ b/src/lib/verifier/prod.ml
@@ -8,70 +8,32 @@ open Blockchain_snark
 
 type invalid = Common.invalid [@@deriving bin_io_unversioned, to_yojson]
 
-module With_id_tag = struct
-  type 'a t = int * 'a [@@deriving bin_io_unversioned]
-
-  let tag_list = List.mapi ~f:(fun id command -> (id, command))
-
-  (* This function associates each tagged inputs with its corresponding result based
-     on the ID, and returns a list of tuples (input, result). *)
-  let reassociate_tagged_results tagged_inputs tagged_results =
-    let result_map = Int.Map.of_alist_exn tagged_results in
-    List.map tagged_inputs ~f:(fun (id, input) ->
-        let result =
-          match Int.Map.find result_map id with
-          | Some res ->
-              res
-          | None ->
-              failwith "Verification result missing for command"
-        in
-        (input, result) )
-end
-
 let invalid_to_error = Common.invalid_to_error
 
 type ledger_proof = Ledger_proof.Prod.t
 
 module Processor = struct
-  let verify_commands
-      (cs : User_command.Verifiable.t With_status.t With_id_tag.t list) :
-      _ list Deferred.t =
-    let results = List.map cs ~f:(fun (id, c) -> (id, Common.check c)) in
+  let verify_commands (cs : User_command.Verifiable.t With_status.t list) =
+    let results = List.map cs ~f:Common.check in
     let to_verify =
-      results |> List.map ~f:snd
-      |> List.concat_map ~f:(function
-           | Ok (_, `Assuming xs) ->
-               xs
-           | Error _ ->
-               [] )
+      List.concat_map
+        ~f:(function Ok (`Assuming xs) -> xs | Error _ -> [])
+        results
     in
     let%map all_verified =
       Pickles.Side_loaded.verify ~typ:Zkapp_statement.typ to_verify
     in
-    List.map results ~f:(fun (id, result) ->
-        let result =
-          match result with
-          | Ok (_, `Assuming []) ->
-              (* The command is dropped here to avoid decoding it later in the caller
-                 which would create a duplicate. Results are paired back to their inputs
-                 using the input [id]*)
-              `Valid
-          | Ok (_, `Assuming xs) ->
-              if Or_error.is_ok all_verified then `Valid else `Valid_assuming xs
-          | Error (`Invalid_keys keys) ->
-              `Invalid_keys keys
-          | Error (`Invalid_signature keys) ->
-              `Invalid_signature keys
-          | Error (`Invalid_proof err) ->
-              `Invalid_proof err
-          | Error (`Missing_verification_key keys) ->
-              `Missing_verification_key keys
-          | Error (`Unexpected_verification_key keys) ->
-              `Unexpected_verification_key keys
-          | Error (`Mismatched_authorization_kind keys) ->
-              `Mismatched_authorization_kind keys
-        in
-        (id, result) )
+    let f : _ -> [ invalid | `Valid | `Valid_assuming of _ ] = function
+      | Error (#invalid as invalid) ->
+          invalid
+      | Ok (`Assuming []) ->
+          (* The command is dropped here to avoid decoding it later in the caller
+             which would create a duplicate.*)
+          `Valid
+      | Ok (`Assuming xs) ->
+          if Or_error.is_ok all_verified then `Valid else `Valid_assuming xs
+    in
+    List.map results ~f
 end
 
 module Worker_state = struct
@@ -80,15 +42,14 @@ module Worker_state = struct
       (Protocol_state.Value.t * Proof.t) list -> unit Or_error.t Deferred.t
 
     val verify_commands :
-         Mina_base.User_command.Verifiable.t With_status.t With_id_tag.t list
+         User_command.Verifiable.t With_status.t list
       -> [ `Valid
          | `Valid_assuming of
            ( Pickles.Side_loaded.Verification_key.t
-           * Mina_base.Zkapp_statement.t
+           * Zkapp_statement.t
            * Pickles.Side_loaded.Proof.t )
            list
          | invalid ]
-         With_id_tag.t
          list
          Deferred.t
 
@@ -186,28 +147,16 @@ module Worker_state = struct
            end in
           (module M : S) )
     | Check | No_check ->
+        let f : _ -> [> invalid | `Valid ] = function
+          | Error (#invalid as invalid) ->
+              invalid
+          | Ok (`Assuming _) ->
+              `Valid
+        in
         Deferred.return
         @@ ( module struct
              let verify_commands tagged_commands =
-               List.map tagged_commands ~f:(fun (id, c) ->
-                   let result =
-                     match Common.check c with
-                     | Ok (_, `Assuming _) ->
-                         `Valid
-                     | Error (`Invalid_keys keys) ->
-                         `Invalid_keys keys
-                     | Error (`Invalid_signature keys) ->
-                         `Invalid_signature keys
-                     | Error (`Invalid_proof err) ->
-                         `Invalid_proof err
-                     | Error (`Missing_verification_key keys) ->
-                         `Missing_verification_key keys
-                     | Error (`Unexpected_verification_key keys) ->
-                         `Unexpected_verification_key keys
-                     | Error (`Mismatched_authorization_kind keys) ->
-                         `Mismatched_authorization_kind keys
-                   in
-                   (id, result) )
+               List.map tagged_commands ~f:(Fn.compose f Common.check)
                |> Deferred.return
 
              let verify_blockchain_snarks _ = Deferred.return (Ok ())
@@ -232,15 +181,14 @@ module Worker = struct
           ('w, (Transaction_snark.t * Sok_message.t) list, unit Or_error.t) F.t
       ; verify_commands :
           ( 'w
-          , User_command.Verifiable.t With_status.t With_id_tag.t list
+          , User_command.Verifiable.t With_status.t list
           , [ `Valid
             | `Valid_assuming of
               ( Pickles.Side_loaded.Verification_key.t
-              * Mina_base.Zkapp_statement.t
+              * Zkapp_statement.t
               * Pickles.Side_loaded.Proof.t )
               list
             | invalid ]
-            With_id_tag.t
             list )
           F.t
       ; toggle_internal_tracing : ('w, bool, unit) F.t
@@ -308,18 +256,15 @@ module Worker = struct
         ; verify_commands =
             f
               ( [%bin_type_class:
-                  User_command.Verifiable.t With_status.Stable.Latest.t
-                  With_id_tag.t
-                  list]
+                  User_command.Verifiable.t With_status.Stable.Latest.t list]
               , [%bin_type_class:
                   [ `Valid
                   | `Valid_assuming of
                     ( Pickles.Side_loaded.Verification_key.Stable.Latest.t
-                    * Mina_base.Zkapp_statement.Stable.Latest.t
+                    * Zkapp_statement.Stable.Latest.t
                     * Pickles.Side_loaded.Proof.Stable.Latest.t )
                     list
                   | invalid ]
-                  With_id_tag.t
                   list]
               , verify_commands )
         ; toggle_internal_tracing =
@@ -660,7 +605,7 @@ let verify_transaction_snarks =
 
 (* Reinjects the original user commands into the validation results.
    This avoids duplicating proof data by not sending it back from the subprocess. *)
-let reinject_valid_user_command_into_valid_result (command, result) =
+let reinject_valid_user_command_into_valid_result command result =
   match result with
   | #invalid as invalid ->
       invalid
@@ -678,28 +623,26 @@ let reinject_valid_user_command_into_valid_result (command, result) =
       `Valid command_valid
 
 let finalize_verification_results tagged_commands tagged_results =
-  With_id_tag.reassociate_tagged_results tagged_commands tagged_results
-  |> List.map ~f:reinject_valid_user_command_into_valid_result
+  List.map2_exn tagged_commands tagged_results
+    ~f:reinject_valid_user_command_into_valid_result
 
-let verify_commands { worker; logger } ts =
-  O1trace.thread "dispatch_user_command_verification" (fun () ->
-      with_retry ~logger (fun () ->
-          let%bind { connection; _ } = Ivar.read !worker in
-          let tagged_commands = With_id_tag.tag_list ts in
-          Worker.Connection.run connection ~f:Worker.functions.verify_commands
-            ~arg:tagged_commands
-          |> Deferred.Or_error.map ~f:(fun tagged_results ->
-                 let results =
-                   finalize_verification_results tagged_commands tagged_results
-                 in
-                 `Continue results ) ) )
+let verify_commands_impl { worker; logger } commands =
+  O1trace.thread "dispatch_user_command_verification"
+  @@ fun () ->
+  with_retry ~logger (fun () ->
+      let%bind { connection; _ } = Ivar.read !worker in
+      Worker.Connection.run connection ~f:Worker.functions.verify_commands
+        ~arg:commands
+      |> Deferred.Or_error.map ~f:(fun results ->
+             let results = finalize_verification_results commands results in
+             `Continue results ) )
 
 let verify_commands t ts =
   let logger = t.logger in
   let count = List.length ts in
   let open Deferred.Let_syntax in
   [%log internal] "Verify_commands" ~metadata:[ ("count", `Int count) ] ;
-  let%map result = verify_commands t ts in
+  let%map result = verify_commands_impl t ts in
   [%log internal] "Verify_commands_done" ;
   result
 


Reviews:
user: login: glyh
body: So there's only one place I have in mind. We change the data being sent over the network due to change in RPC interface. How is the relation between block producer requesting verification and the verifier actually doing the verification? Are they always distributed together so there's no versioning issue? 
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-13T05:07:35Z
user: login: georgeee
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-13T07:43:22Z
user: login: glyh
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-13T07:59:28Z
user: login: glyh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-13T08:00:26Z
user: login: mrmr1993
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T19:26:58Z

Review comments:
diff_hunk: @@ -308,18 +256,15 @@ module Worker = struct
         ; verify_commands =
             f
               ( [%bin_type_class:
-                  User_command.Verifiable.t With_status.Stable.Latest.t
path: src/lib/verifier/prod.ml
user: login: glyh
body: This thing is being serialized, and unversioned. Wouldn't this be a breaking change? 
created_at: 2025-04-13T05:05:24Z
author_association: MEMBER
diff_hunk: @@ -308,18 +256,15 @@ module Worker = struct
         ; verify_commands =
             f
               ( [%bin_type_class:
-                  User_command.Verifiable.t With_status.Stable.Latest.t
path: src/lib/verifier/prod.ml
user: login: georgeee
body: This is unversioned precisely because prover is always using the same binary as the main thread. It's a process that's spawned on start of the application. So it's not possible to have versions diverging in this context.
created_at: 2025-04-13T07:43:22Z
author_association: MEMBER
diff_hunk: @@ -308,18 +256,15 @@ module Worker = struct
         ; verify_commands =
             f
               ( [%bin_type_class:
-                  User_command.Verifiable.t With_status.Stable.Latest.t
path: src/lib/verifier/prod.ml
user: login: glyh
body: I suggest we document this near the PRC :) 
created_at: 2025-04-13T08:00:26Z
author_association: MEMBER

PR comments:
user: login: georgeee
created_at: 2025-04-12T14:59:39Z
body: !ci-bypass-changelog
author_association: MEMBER
user: login: georgeee
created_at: 2025-04-12T14:59:45Z
body: !ci-build-me
author_association: MEMBER
user: login: glyh
created_at: 2025-04-13T04:57:00Z
body: I guess the initial idea for tagging is so we could potentially parallelize verification of different commands? 
author_association: MEMBER