• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MinaProtocol / mina / 538

25 Aug 2025 05:35PM UTC coverage: 61.202% (+0.4%) from 60.772%
538

push

buildkite

web-flow
Merge pull request #17673 from MinaProtocol/amcie-merge-release320-to-master

amcie-merge-release320-to-master

3142 of 4828 new or added lines in 308 files covered. (65.08%)

205 existing lines in 68 files now uncovered.

50733 of 82894 relevant lines covered (61.2%)

470098.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.34
/src/lib/user_command_input/user_command_input.ml
1
open Mina_base
59✔
2
open Signature_lib
3
open Mina_numbers
4
open Core_kernel
5
open Async_kernel
6

7
module Payload = struct
8
  module Common = struct
9
    [%%versioned
10
    module Stable = struct
11
      module V2 = struct
12
        type t =
59✔
13
          ( Currency.Fee.Stable.V1.t
×
14
          , Public_key.Compressed.Stable.V1.t
×
15
          , Account_nonce.Stable.V1.t option
×
16
          , Global_slot_since_genesis.Stable.V1.t
×
17
          , Signed_command_memo.Stable.V1.t )
×
18
          Signed_command_payload.Common.Poly.Stable.V2.t
19
        [@@deriving sexp, to_yojson]
295✔
20

21
        let to_latest = Fn.id
22
      end
23
    end]
24

25
    let create ~fee ~fee_payer_pk ?nonce ~valid_until ~memo : t =
26
      { fee; fee_payer_pk; nonce; valid_until; memo }
×
27

28
    let to_user_command_common (t : t) ~minimum_nonce ~inferred_nonce :
29
        (Signed_command_payload.Common.t, string) Result.t =
30
      let open Result.Let_syntax in
×
31
      let%map nonce =
32
        match t.nonce with
33
        | None ->
×
34
            (*User did not provide a nonce, use inferred*)
35
            Ok inferred_nonce
36
        | Some nonce ->
×
37
            (* NB: A lower, explicitly given nonce can be used to cancel
38
               transactions or to re-issue them with a higher fee.
39
            *)
40
            if Account_nonce.(minimum_nonce <= nonce && nonce <= inferred_nonce)
×
41
            then Ok nonce
×
42
            else
43
              (* IMPORTANT! Do not change the content of this error without
44
               * updating Rosetta's construction API to handle the changes *)
45
              Error
×
46
                (sprintf
×
47
                   !"Input nonce %s either different from inferred nonce %s or \
48
                     below minimum_nonce %s"
49
                   (Account_nonce.to_string nonce)
×
50
                   (Account_nonce.to_string inferred_nonce)
×
51
                   (Account_nonce.to_string minimum_nonce) )
×
52
      in
53
      { Signed_command_payload.Common.Poly.fee = t.fee
×
54
      ; fee_payer_pk = t.fee_payer_pk
55
      ; nonce
56
      ; valid_until = t.valid_until
57
      ; memo = t.memo
58
      }
59

60
    let fee_payer ({ fee_payer_pk; _ } : t) =
61
      Account_id.create fee_payer_pk Mina_base.Token_id.default
×
62
  end
63

64
  [%%versioned
65
  module Stable = struct
66
    module V2 = struct
67
      type t =
59✔
68
        ( Common.Stable.V2.t
×
69
        , Signed_command_payload.Body.Stable.V2.t )
×
70
        Signed_command_payload.Poly.Stable.V1.t
71
      [@@deriving sexp, to_yojson]
295✔
72

73
      let to_latest = Fn.id
74
    end
75
  end]
76

77
  let create ~fee ~fee_payer_pk ?nonce ~valid_until ~memo ~body : t =
78
    { common = Common.create ~fee ~fee_payer_pk ?nonce ~valid_until ~memo
×
79
    ; body
80
    }
81

82
  let to_user_command_payload (t : t) ~minimum_nonce ~inferred_nonce :
83
      (Signed_command_payload.t, string) Result.t =
84
    let open Result.Let_syntax in
×
85
    let%map common =
86
      Common.to_user_command_common t.common ~minimum_nonce ~inferred_nonce
×
87
    in
88
    { Signed_command_payload.Poly.common; body = t.body }
×
89

90
  let fee_payer ({ common; _ } : t) = Common.fee_payer common
×
91
end
92

93
module Sign_choice = struct
94
  [%%versioned
95
  module Stable = struct
96
    module V1 = struct
97
      type t =
118✔
98
        | Signature of Signature.Stable.V1.t
×
99
        | Hd_index of Unsigned_extended.UInt32.Stable.V1.t
×
100
        | Keypair of Keypair.Stable.V1.t
×
101
      [@@deriving sexp, to_yojson]
295✔
102

103
      let to_latest = Fn.id
104
    end
105
  end]
106
end
107

108
[%%versioned
109
module Stable = struct
110
  module V2 = struct
111
    type t =
59✔
112
      ( Payload.Stable.V2.t
×
113
      , Public_key.Compressed.Stable.V1.t
×
114
      , Sign_choice.Stable.V1.t )
×
115
      Signed_command.Poly.Stable.V1.t
116
    [@@deriving sexp, to_yojson]
295✔
117

118
    let to_latest = Fn.id
119
  end
120
end]
121

122
[%%define_locally Stable.Latest.(to_yojson)]
123

124
let fee_payer ({ payload; _ } : t) = Payload.fee_payer payload
×
125

126
let create ?nonce ~fee ~fee_payer_pk ~valid_until ~memo ~body ~signer
127
    ~sign_choice () : t =
128
  let valid_until =
×
129
    Option.value valid_until ~default:Global_slot_since_genesis.max_value
130
  in
131
  let payload =
×
132
    Payload.create ~fee ~fee_payer_pk ?nonce ~valid_until ~memo ~body
133
  in
134
  { payload; signer; signature = sign_choice }
135

136
let sign ~signer ~(user_command_payload : Signed_command_payload.t) =
NEW
137
  let signature_kind = Mina_signature_kind.t_DEPRECATED in
×
138
  function
UNCOV
139
  | Sign_choice.Signature signature ->
×
140
      Option.value_map
141
        ~default:(Deferred.return (Error "Invalid_signature"))
×
NEW
142
        (Signed_command.create_with_signature_checked ~signature_kind signature
×
143
           signer user_command_payload )
144
        ~f:Deferred.Result.return
145
  | Keypair signer_kp ->
×
146
      Deferred.Result.return
NEW
147
        (Signed_command.sign ~signature_kind signer_kp user_command_payload)
×
148
  | Hd_index hd_index ->
×
149
      Secrets.Hardware_wallets.sign ~hd_index
150
        ~public_key:(Public_key.decompress_exn signer)
×
151
        ~user_command_payload
152

153
let inferred_nonce ~get_current_nonce ~(fee_payer : Account_id.t) ~nonce_map =
154
  let open Result.Let_syntax in
×
155
  let update_map = Map.set nonce_map ~key:fee_payer in
156
  match Map.find nonce_map fee_payer with
×
157
  | Some (min_nonce, nonce) ->
×
158
      (* Multiple user commands from the same fee-payer. *)
159
      (* TODO: this logic does not currently support zkapp_command transactions, as zkapp_command transactions can increment the fee payer nonce more than once (#11001) *)
160
      let next_nonce = Account_nonce.succ nonce in
161
      let updated_map = update_map ~data:(min_nonce, next_nonce) in
×
162
      Ok (min_nonce, next_nonce, updated_map)
163
  | None ->
×
164
      let%map `Min min_nonce, txn_pool_or_account_nonce =
165
        get_current_nonce fee_payer
×
166
      in
167
      let updated_map =
×
168
        update_map ~data:(min_nonce, txn_pool_or_account_nonce)
169
      in
170
      (min_nonce, txn_pool_or_account_nonce, updated_map)
171

172
(* If the receiver account doesn't exist yet (as far as we can tell) *and* the
173
 * user command isn't sufficient to cover the account creation fee, log a
174
 * warning. *)
175
let warn_if_unable_to_pay_account_creation_fee ~get_account
176
    ~(constraint_constants : Genesis_constants.Constraint_constants.t) ~logger
177
    user_command_payload =
178
  let receiver_pk = Signed_command_payload.receiver_pk user_command_payload in
×
179
  let token = Signed_command_payload.token user_command_payload in
×
180
  let receiver = Account_id.create receiver_pk token in
×
181
  let receiver_account = get_account receiver in
×
182
  let amount = Signed_command_payload.amount user_command_payload in
×
183
  match (receiver_account, amount) with
×
184
  | `Bootstrapping, _ | `Active (Some _), _ | _, None ->
×
185
      ()
186
  | `Active None, Some amount ->
×
187
      let open Currency.Amount in
188
      let account_creation_fee =
189
        of_fee constraint_constants.account_creation_fee
190
      in
191
      if amount < account_creation_fee then
×
192
        [%log warn]
×
193
          "A transaction was submitted that is likely to fail because the \
194
           receiver account doesn't appear to have been created already and \
195
           the transaction amount of %s is smaller than the account creation \
196
           fee of %s."
197
          (to_mina_string amount)
×
198
          (to_mina_string account_creation_fee) ;
×
199
      ()
×
200

201
let to_user_command ?(nonce_map = Account_id.Map.empty) ~get_current_nonce
×
202
    ~get_account ~constraint_constants ~logger (client_input : t) =
203
  Deferred.map
×
204
    ~f:
205
      (Result.map_error ~f:(fun str ->
206
           Error.createf "Error creating user command: %s Error: %s"
×
207
             (Yojson.Safe.to_string (to_yojson client_input))
×
208
             str ) )
209
  @@
210
  let open Deferred.Result.Let_syntax in
211
  let fee_payer = fee_payer client_input in
212
  let%bind minimum_nonce, inferred_nonce, updated_nonce_map =
213
    inferred_nonce ~get_current_nonce ~fee_payer ~nonce_map |> Deferred.return
×
214
  in
215
  let%bind user_command_payload =
216
    Payload.to_user_command_payload client_input.payload ~minimum_nonce
×
217
      ~inferred_nonce
218
    |> Deferred.return
×
219
  in
220
  let () =
×
221
    warn_if_unable_to_pay_account_creation_fee ~get_account
222
      ~constraint_constants ~logger user_command_payload
223
  in
224
  let%map signed_user_command =
225
    sign ~signer:client_input.signer ~user_command_payload
×
226
      client_input.signature
227
  in
228
  (Signed_command.forget_check signed_user_command, updated_nonce_map)
×
229

230
let to_user_commands ?(nonce_map = Account_id.Map.empty) ~get_current_nonce
×
231
    ~get_account ~constraint_constants ~logger uc_inputs :
232
    Signed_command.t list Deferred.Or_error.t =
233
  (* When batching multiple user commands, keep track of the nonces and send
234
      all the user commands if they are valid or none if there is an error in
235
      one of them.
236
  *)
237
  let open Deferred.Or_error.Let_syntax in
×
238
  let%map user_commands, _ =
239
    Deferred.Or_error.List.fold ~init:([], nonce_map) uc_inputs
×
240
      ~f:(fun (valid_user_commands, nonce_map) uc_input ->
241
        let%map res, updated_nonce_map =
242
          to_user_command ~nonce_map ~get_current_nonce ~get_account
×
243
            ~constraint_constants ~logger uc_input
244
        in
245
        (res :: valid_user_commands, updated_nonce_map) )
×
246
  in
247
  List.rev user_commands
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc